Use sync code in allow/deny for 2.x

This commit is contained in:
Harry Adel 2026-02-19 00:26:47 +02:00
parent f934aea2a5
commit e77be37450
28 changed files with 594 additions and 410 deletions

187
METEOR3_MIGRATION.md Normal file
View file

@ -0,0 +1,187 @@
# Meteor 3.0 Migration Guide
Reference document capturing patterns, constraints, and lessons learned during the async migration of WeKan from Meteor 2.16 toward Meteor 3.0 readiness.
---
## 1. Dual-Compatibility Strategy
WeKan runs on **Meteor 2.16 with Blaze 2.x**. The goal is dual compatibility: changes must work on 2.16 now and remain compatible with a future Meteor 3.0 upgrade.
**Key constraint:** Blaze 2.x does NOT support async template helpers. Client-side code must receive synchronous data.
---
## 2. ReactiveCache Facade Pattern
`ReactiveCache` dispatches to `ReactiveCacheServer` (async MongoDB) or `ReactiveCacheClient` (sync Minimongo).
**Rule:** Facade methods must NOT be `async`. They return a Promise on the server and data on the client. Server callers `await`; client code uses the return value directly.
```javascript
// CORRECT:
getBoard(boardId) {
if (Meteor.isServer) {
return ReactiveCacheServer.getBoard(boardId); // Returns Promise
} else {
return ReactiveCacheClient.getBoard(boardId); // Returns data
}
}
// WRONG:
async getBoard(boardId) { ... } // Wraps client return in Promise too!
```
---
## 3. Model Helpers (Collection.helpers)
Model helpers defined via `Collection.helpers({})` are used by Blaze templates. They must NOT be `async`.
```javascript
// CORRECT:
Cards.helpers({
board() {
return ReactiveCache.getBoard(this.boardId); // Promise on server, data on client
},
});
// WRONG:
Cards.helpers({
async board() { // Blaze gets Promise instead of data
return await ReactiveCache.getBoard(this.boardId);
},
});
```
**Server-side callers** of these helpers must `await` the result:
```javascript
// In a Meteor method or hook (server-only):
const board = await card.board();
```
---
## 4. Allow/Deny Callbacks Must Be Synchronous
Meteor 2.x evaluates allow/deny callbacks synchronously. An `async` callback returns a Promise:
- **allow** callback returning Promise (truthy) → always passes
- **deny** callback returning Promise (truthy) → always denies
**Rule:** Never use `async` in allow/deny. Replace `ReactiveCache` calls with direct sync Mongo calls.
```javascript
// CORRECT:
Cards.allow({
insert(userId, doc) {
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});
// WRONG:
Cards.allow({
async insert(userId, doc) {
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId));
},
});
```
### Sync alternatives for common patterns:
| Async (broken in allow/deny) | Sync replacement |
|------------------------------|------------------|
| `await ReactiveCache.getBoard(id)` | `Boards.findOne(id)` |
| `await ReactiveCache.getCard(id)` | `Cards.findOne(id)` |
| `await ReactiveCache.getCurrentUser()` | `Meteor.users.findOne(userId)` |
| `await ReactiveCache.getBoards({...})` | `Boards.find({...}).fetch()` |
| `await card.board()` | `Boards.findOne(card.boardId)` |
**Note:** These sync Mongo calls (`findOne`, `find().fetch()`) are available in Meteor 2.x. In Meteor 3.0, they will be replaced by `findOneAsync` / `find().fetchAsync()`, which will require allow/deny callbacks to be reworked again (or replaced by Meteor 3.0's new permission model).
---
## 5. Server-Only Code CAN Be Async
Code that runs exclusively on the server can safely use `async`/`await`:
- `Meteor.methods({})` — method bodies
- `Meteor.publish()` — publication functions
- `JsonRoutes.add()` — REST API handlers
- `Collection.before.*` / `Collection.after.*` — collection hooks (via `matb33:collection-hooks`)
- Standalone server functions
```javascript
Meteor.methods({
async createCard(data) {
const board = await ReactiveCache.getBoard(data.boardId); // OK
// ...
},
});
```
---
## 6. forEach with await Anti-Pattern
`Array.forEach()` does not handle async callbacks — iterations run concurrently without awaiting.
```javascript
// WRONG:
items.forEach(async (item) => {
await processItem(item); // Runs all in parallel, not sequentially
});
// CORRECT:
for (const item of items) {
await processItem(item); // Runs sequentially
}
```
---
## 7. Client-Side Collection Updates
Meteor requires client-side collection updates to use `_id` as the selector:
```javascript
// CORRECT:
Lists.updateAsync(listId, { $set: { title: newTitle } });
// WRONG - fails with "Untrusted code may only update documents by ID":
Lists.updateAsync({ _id: listId, boardId: boardId }, { $set: { title: newTitle } });
```
---
## 8. Sync Meteor 2.x APIs to Convert for 3.0
These Meteor 2.x sync APIs will need conversion when upgrading to Meteor 3.0:
| Meteor 2.x (sync) | Meteor 3.0 (async) |
|--------------------|--------------------|
| `Collection.findOne()` | `Collection.findOneAsync()` |
| `Collection.find().fetch()` | `Collection.find().fetchAsync()` |
| `Collection.insert()` | `Collection.insertAsync()` |
| `Collection.update()` | `Collection.updateAsync()` |
| `Collection.remove()` | `Collection.removeAsync()` |
| `Collection.upsert()` | `Collection.upsertAsync()` |
| `Meteor.user()` | `Meteor.userAsync()` |
| `Meteor.userId()` | Remains sync |
**Current status:** Server-side code already uses async patterns via `ReactiveCache`. The sync `findOne()` calls in allow/deny callbacks will need to be addressed when Meteor 3.0's allow/deny system supports async (or is replaced).
---
## 9. Files Reference
Key files involved in the async migration:
| File | Role |
|------|------|
| `imports/reactiveCache.js` | ReactiveCache facade + Server/Client/Index implementations |
| `server/lib/utils.js` | Permission helper functions (`allowIsBoardMember*`) |
| `models/*.js` | Collection schemas, helpers, allow/deny, hooks, methods |
| `server/publications/*.js` | Meteor publications |
| `server/rulesHelper.js` | Rule trigger/action evaluation |
| `server/cronMigrationManager.js` | Cron-based migration jobs |

View file

@ -46,8 +46,8 @@ AccessibilitySettings.attachSchema(
); );
AccessibilitySettings.allow({ AccessibilitySettings.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -45,8 +45,8 @@ AccountSettings.attachSchema(
); );
AccountSettings.allow({ AccountSettings.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -4,14 +4,14 @@ import { Meteor } from 'meteor/meteor';
Actions = new Mongo.Collection('actions'); Actions = new Mongo.Collection('actions');
Actions.allow({ Actions.allow({
async insert(userId, doc) { insert(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
}); });

View file

@ -118,7 +118,7 @@ if (Meteor.isServer) {
if (activity.userId) { if (activity.userId) {
// No need send notification to user of activity // No need send notification to user of activity
// participants = _.union(participants, [activity.userId]); // participants = _.union(participants, [activity.userId]);
const user = activity.user(); const user = await activity.user();
if (user) { if (user) {
if (user.getName()) { if (user.getName()) {
params.user = user.getName(); params.user = user.getName();
@ -146,7 +146,7 @@ if (Meteor.isServer) {
params.boardId = activity.boardId; params.boardId = activity.boardId;
} }
if (activity.oldBoardId) { if (activity.oldBoardId) {
const oldBoard = activity.oldBoard(); const oldBoard = await activity.oldBoard();
if (oldBoard) { if (oldBoard) {
watchers = _.union(watchers, oldBoard.watchers || []); watchers = _.union(watchers, oldBoard.watchers || []);
params.oldBoard = oldBoard.title; params.oldBoard = oldBoard.title;
@ -155,10 +155,10 @@ if (Meteor.isServer) {
} }
if (activity.memberId) { if (activity.memberId) {
participants = _.union(participants, [activity.memberId]); participants = _.union(participants, [activity.memberId]);
params.member = activity.member().getName(); params.member = (await activity.member()).getName();
} }
if (activity.listId) { if (activity.listId) {
const list = activity.list(); const list = await activity.list();
if (list) { if (list) {
if (list.watchers !== undefined) { if (list.watchers !== undefined) {
watchers = _.union(watchers, list.watchers || []); watchers = _.union(watchers, list.watchers || []);
@ -168,7 +168,7 @@ if (Meteor.isServer) {
} }
} }
if (activity.oldListId) { if (activity.oldListId) {
const oldList = activity.oldList(); const oldList = await activity.oldList();
if (oldList) { if (oldList) {
watchers = _.union(watchers, oldList.watchers || []); watchers = _.union(watchers, oldList.watchers || []);
params.oldList = oldList.title; params.oldList = oldList.title;
@ -176,7 +176,7 @@ if (Meteor.isServer) {
} }
} }
if (activity.oldSwimlaneId) { if (activity.oldSwimlaneId) {
const oldSwimlane = activity.oldSwimlane(); const oldSwimlane = await activity.oldSwimlane();
if (oldSwimlane) { if (oldSwimlane) {
watchers = _.union(watchers, oldSwimlane.watchers || []); watchers = _.union(watchers, oldSwimlane.watchers || []);
params.oldSwimlane = oldSwimlane.title; params.oldSwimlane = oldSwimlane.title;
@ -184,7 +184,7 @@ if (Meteor.isServer) {
} }
} }
if (activity.cardId) { if (activity.cardId) {
const card = activity.card(); const card = await activity.card();
participants = _.union(participants, [card.userId], card.members || []); participants = _.union(participants, [card.userId], card.members || []);
watchers = _.union(watchers, card.watchers || []); watchers = _.union(watchers, card.watchers || []);
params.card = card.title; params.card = card.title;
@ -193,12 +193,12 @@ if (Meteor.isServer) {
params.cardId = activity.cardId; params.cardId = activity.cardId;
} }
if (activity.swimlaneId) { if (activity.swimlaneId) {
const swimlane = activity.swimlane(); const swimlane = await activity.swimlane();
params.swimlane = swimlane.title; params.swimlane = swimlane.title;
params.swimlaneId = activity.swimlaneId; params.swimlaneId = activity.swimlaneId;
} }
if (activity.commentId) { if (activity.commentId) {
const comment = activity.comment(); const comment = await activity.comment();
params.comment = comment.text; params.comment = comment.text;
let hasMentions = false; // Track if comment has @mentions let hasMentions = false; // Track if comment has @mentions
if (board) { if (board) {
@ -257,7 +257,7 @@ if (Meteor.isServer) {
hasMentions = true; hasMentions = true;
} else if (activity.cardId && username === 'card_members') { } else if (activity.cardId && username === 'card_members') {
// mentions all card members if assigned // mentions all card members if assigned
const card = activity.card(); const card = await activity.card();
if (card && card.members && card.members.length > 0) { if (card && card.members && card.members.length > 0) {
// Filter to only valid users who are board members // Filter to only valid users who are board members
const validMembers = []; const validMembers = [];
@ -273,7 +273,7 @@ if (Meteor.isServer) {
hasMentions = true; hasMentions = true;
} else if (activity.cardId && username === 'card_assignees') { } else if (activity.cardId && username === 'card_assignees') {
// mentions all assignees of the current card // mentions all assignees of the current card
const card = activity.card(); const card = await activity.card();
if (card && card.assignees && card.assignees.length > 0) { if (card && card.assignees && card.assignees.length > 0) {
// Filter to only valid users who are board members // Filter to only valid users who are board members
const validAssignees = []; const validAssignees = [];
@ -310,7 +310,7 @@ if (Meteor.isServer) {
params.attachmentId = activity.attachmentId; params.attachmentId = activity.attachmentId;
} }
if (activity.checklistId) { if (activity.checklistId) {
const checklist = activity.checklist(); const checklist = await activity.checklist();
if (checklist) { if (checklist) {
if (checklist.title) { if (checklist.title) {
params.checklist = checklist.title; params.checklist = checklist.title;
@ -318,7 +318,7 @@ if (Meteor.isServer) {
} }
} }
if (activity.checklistItemId) { if (activity.checklistItemId) {
const checklistItem = activity.checklistItem(); const checklistItem = await activity.checklistItem();
if (checklistItem) { if (checklistItem) {
if (checklistItem.title) { if (checklistItem.title) {
params.checklistItem = checklistItem.title; params.checklistItem = checklistItem.title;
@ -326,7 +326,7 @@ if (Meteor.isServer) {
} }
} }
if (activity.customFieldId) { if (activity.customFieldId) {
const customField = activity.customField(); const customField = await activity.customField();
if (customField) { if (customField) {
if (customField.name) { if (customField.name) {
params.customField = customField.name; params.customField = customField.name;
@ -338,7 +338,7 @@ if (Meteor.isServer) {
} }
// Label activity did not work yet, unable to edit labels when tried this. // Label activity did not work yet, unable to edit labels when tried this.
if (activity.labelId) { if (activity.labelId) {
const label = activity.label(); const label = await activity.label();
if (label) { if (label) {
if (label.name) { if (label.name) {
params.label = label.name; params.label = label.name;
@ -368,10 +368,8 @@ if (Meteor.isServer) {
try { try {
const atype = activity.activityType; const atype = activity.activityType;
if (new RegExp(BIGEVENTS).exec(atype)) { if (new RegExp(BIGEVENTS).exec(atype)) {
watchers = _.union( const activeMemberIds = _.filter(board.members, m => m.isActive === true).map(m => m.userId);
watchers, watchers = _.union(watchers, activeMemberIds); // notify all active members for important events
board.activeMembers().map((member) => member.userId),
); // notify all active members for important events
} }
} catch (e) { } catch (e) {
// passed env var BIGEVENTS_PATTERN is not a valid regex // passed env var BIGEVENTS_PATTERN is not a valid regex
@ -396,7 +394,7 @@ if (Meteor.isServer) {
); );
} }
} }
Notifications.getUsers(watchers).forEach((user) => { (await Notifications.getUsers(watchers)).forEach((user) => {
// Skip if user is undefined or doesn't have an _id (e.g., deleted user or invalid ID) // Skip if user is undefined or doesn't have an _id (e.g., deleted user or invalid ID)
if (!user || !user._id) return; if (!user || !user._id) return;

View file

@ -50,8 +50,8 @@ Announcements.attachSchema(
); );
Announcements.allow({ Announcements.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -196,11 +196,11 @@ Attachments = new FilesCollection({
if (Meteor.isServer) { if (Meteor.isServer) {
Attachments.allow({ Attachments.allow({
async insert(userId, fileObj) { insert(userId, fileObj) {
// ReadOnly users cannot upload attachments // ReadOnly users cannot upload attachments
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(fileObj.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId));
}, },
async update(userId, fileObj, fields) { update(userId, fileObj, fields) {
// SECURITY: The 'name' field is sanitized in onBeforeUpload and server-side methods, // SECURITY: The 'name' field is sanitized in onBeforeUpload and server-side methods,
// but we block direct client-side $set operations on 'versions.*.path' to prevent // but we block direct client-side $set operations on 'versions.*.path' to prevent
// path traversal attacks via storage migration exploits. // path traversal attacks via storage migration exploits.
@ -230,9 +230,9 @@ if (Meteor.isServer) {
} }
// ReadOnly users cannot update attachments // ReadOnly users cannot update attachments
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(fileObj.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId));
}, },
async remove(userId, fileObj) { remove(userId, fileObj) {
// Additional security check: ensure the file belongs to the board the user has access to // Additional security check: ensure the file belongs to the board the user has access to
if (!fileObj || !fileObj.boardId) { if (!fileObj || !fileObj.boardId) {
if (process.env.DEBUG === 'true') { if (process.env.DEBUG === 'true') {
@ -241,7 +241,7 @@ if (Meteor.isServer) {
return false; return false;
} }
const board = await ReactiveCache.getBoard(fileObj.boardId); const board = Boards.findOne(fileObj.boardId);
if (!board) { if (!board) {
if (process.env.DEBUG === 'true') { if (process.env.DEBUG === 'true') {
console.warn('Blocked attachment removal: board not found'); console.warn('Blocked attachment removal: board not found');

View file

@ -1828,7 +1828,7 @@ Boards.labelColors = () => {
if (Meteor.isServer) { if (Meteor.isServer) {
Boards.allow({ Boards.allow({
async insert(userId, doc) { insert(userId, doc) {
// Check if user is logged in // Check if user is logged in
if (!userId) return false; if (!userId) return false;
@ -1847,7 +1847,7 @@ if (Meteor.isServer) {
// All logged in users are allowed to reorder boards by dragging at All Boards page and Public Boards page. // All logged in users are allowed to reorder boards by dragging at All Boards page and Public Boards page.
Boards.allow({ Boards.allow({
async update(userId, board, fieldNames) { update(userId, board, fieldNames) {
return canUpdateBoardSort(userId, board, fieldNames); return canUpdateBoardSort(userId, board, fieldNames);
}, },
// Need members to verify membership in policy // Need members to verify membership in policy
@ -1857,7 +1857,7 @@ if (Meteor.isServer) {
// The number of users that have starred this board is managed by trusted code // The number of users that have starred this board is managed by trusted code
// and the user is not allowed to update it // and the user is not allowed to update it
Boards.deny({ Boards.deny({
async update(userId, board, fieldNames) { update(userId, board, fieldNames) {
return _.contains(fieldNames, 'stars'); return _.contains(fieldNames, 'stars');
}, },
fetch: [], fetch: [],
@ -1865,7 +1865,7 @@ if (Meteor.isServer) {
// We can't remove a member if it is the last administrator // We can't remove a member if it is the last administrator
Boards.deny({ Boards.deny({
async update(userId, doc, fieldNames, modifier) { update(userId, doc, fieldNames, modifier) {
if (!_.contains(fieldNames, 'members')) return false; if (!_.contains(fieldNames, 'members')) return false;
// We only care in case of a $pull operation, ie remove a member // We only care in case of a $pull operation, ie remove a member
@ -1891,7 +1891,7 @@ if (Meteor.isServer) {
// Deny changing permission to public if allowPrivateOnly is enabled // Deny changing permission to public if allowPrivateOnly is enabled
Boards.deny({ Boards.deny({
async update(userId, doc, fieldNames, modifier) { update(userId, doc, fieldNames, modifier) {
if (!_.contains(fieldNames, 'permission')) return false; if (!_.contains(fieldNames, 'permission')) return false;
const allowPrivateOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly')?.booleanValue; const allowPrivateOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly')?.booleanValue;

View file

@ -50,14 +50,14 @@ CardCommentReactions.attachSchema(
); );
CardCommentReactions.allow({ CardCommentReactions.allow({
async insert(userId, doc) { insert(userId, doc) {
return allowIsBoardMember(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
return allowIsBoardMember(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
return allowIsBoardMember(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
}, },
fetch: ['boardId'], fetch: ['boardId'],
}); });

View file

@ -81,15 +81,15 @@ CardComments.attachSchema(
); );
CardComments.allow({ CardComments.allow({
async insert(userId, doc) { insert(userId, doc) {
// ReadOnly users cannot add comments. Only members who can comment are allowed. // ReadOnly users cannot add comments. Only members who can comment are allowed.
return allowIsBoardMemberCommentOnly(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
return userId === doc.userId || allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
return userId === doc.userId || allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
fetch: ['userId', 'boardId'], fetch: ['userId', 'boardId'],
}); });

File diff suppressed because it is too large Load diff

View file

@ -69,17 +69,17 @@ ChecklistItems.attachSchema(
); );
ChecklistItems.allow({ ChecklistItems.allow({
async insert(userId, doc) { insert(userId, doc) {
// ReadOnly users cannot create checklist items // ReadOnly users cannot create checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
async update(userId, doc) { update(userId, doc) {
// ReadOnly users cannot edit checklist items // ReadOnly users cannot edit checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
// ReadOnly users cannot delete checklist items // ReadOnly users cannot delete checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
fetch: ['userId', 'cardId'], fetch: ['userId', 'cardId'],
}); });

View file

@ -195,17 +195,17 @@ Checklists.helpers({
}); });
Checklists.allow({ Checklists.allow({
async insert(userId, doc) { insert(userId, doc) {
// ReadOnly users cannot create checklists // ReadOnly users cannot create checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
async update(userId, doc) { update(userId, doc) {
// ReadOnly users cannot edit checklists // ReadOnly users cannot edit checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
// ReadOnly users cannot delete checklists // ReadOnly users cannot delete checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, await ReactiveCache.getCard(doc.cardId)); return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
}, },
fetch: ['userId', 'cardId'], fetch: ['userId', 'cardId'],
}); });

View file

@ -164,28 +164,28 @@ CustomFields.helpers({
}); });
CustomFields.allow({ CustomFields.allow({
async insert(userId, doc) { insert(userId, doc) {
return allowIsAnyBoardMember( return allowIsAnyBoardMember(
userId, userId,
await ReactiveCache.getBoards({ Boards.find({
_id: { $in: doc.boardIds }, _id: { $in: doc.boardIds },
}), }).fetch(),
); );
}, },
async update(userId, doc) { update(userId, doc) {
return allowIsAnyBoardMember( return allowIsAnyBoardMember(
userId, userId,
await ReactiveCache.getBoards({ Boards.find({
_id: { $in: doc.boardIds }, _id: { $in: doc.boardIds },
}), }).fetch(),
); );
}, },
async remove(userId, doc) { remove(userId, doc) {
return allowIsAnyBoardMember( return allowIsAnyBoardMember(
userId, userId,
await ReactiveCache.getBoards({ Boards.find({
_id: { $in: doc.boardIds }, _id: { $in: doc.boardIds },
}), }).fetch(),
); );
}, },
fetch: ['userId', 'boardIds'], fetch: ['userId', 'boardIds'],

View file

@ -101,21 +101,21 @@ Integrations.Const = {
}, },
}; };
const permissionHelper = { const permissionHelper = {
async allow(userId, doc) { allow(userId, doc) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
const isAdmin = user && (await ReactiveCache.getCurrentUser()).isAdmin; const isAdmin = user && user.isAdmin;
return isAdmin || allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return isAdmin || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
}; };
Integrations.allow({ Integrations.allow({
async insert(userId, doc) { insert(userId, doc) {
return await permissionHelper.allow(userId, doc); return permissionHelper.allow(userId, doc);
}, },
async update(userId, doc) { update(userId, doc) {
return await permissionHelper.allow(userId, doc); return permissionHelper.allow(userId, doc);
}, },
async remove(userId, doc) { remove(userId, doc) {
return await permissionHelper.allow(userId, doc); return permissionHelper.allow(userId, doc);
}, },
fetch: ['boardId'], fetch: ['boardId'],
}); });

View file

@ -180,17 +180,17 @@ Lists.attachSchema(
); );
Lists.allow({ Lists.allow({
async insert(userId, doc) { insert(userId, doc) {
// ReadOnly and CommentOnly users cannot create lists // ReadOnly and CommentOnly users cannot create lists
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
// ReadOnly and CommentOnly users cannot edit lists // ReadOnly and CommentOnly users cannot edit lists
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
// ReadOnly and CommentOnly users cannot delete lists // ReadOnly and CommentOnly users cannot delete lists
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
fetch: ['boardId'], fetch: ['boardId'],
}); });
@ -540,7 +540,7 @@ Meteor.methods({
} }
await Lists.updateAsync( await Lists.updateAsync(
{ _id: listId, boardId }, listId,
{ {
$set: { $set: {
...updateData, ...updateData,

View file

@ -48,8 +48,8 @@ LockoutSettings.attachSchema(
); );
LockoutSettings.allow({ LockoutSettings.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -87,8 +87,8 @@ Org.attachSchema(
if (Meteor.isServer) { if (Meteor.isServer) {
Org.allow({ Org.allow({
async insert(userId, doc) { insert(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -96,8 +96,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async update(userId, doc) { update(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -105,8 +105,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async remove(userId, doc) { remove(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {

View file

@ -72,14 +72,14 @@ Rules.helpers({
}); });
Rules.allow({ Rules.allow({
async insert(userId, doc) { insert(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
}); });

View file

@ -223,8 +223,8 @@ Settings.helpers({
}, },
}); });
Settings.allow({ Settings.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -131,17 +131,17 @@ Swimlanes.attachSchema(
); );
Swimlanes.allow({ Swimlanes.allow({
async insert(userId, doc) { insert(userId, doc) {
// ReadOnly and CommentOnly users cannot create swimlanes // ReadOnly and CommentOnly users cannot create swimlanes
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
// ReadOnly and CommentOnly users cannot edit swimlanes // ReadOnly and CommentOnly users cannot edit swimlanes
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
// ReadOnly and CommentOnly users cannot delete swimlanes // ReadOnly and CommentOnly users cannot delete swimlanes
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
}, },
fetch: ['boardId'], fetch: ['boardId'],
}); });

View file

@ -45,8 +45,8 @@ TableVisibilityModeSettings.attachSchema(
); );
TableVisibilityModeSettings.allow({ TableVisibilityModeSettings.allow({
async update(userId) { update(userId) {
const user = await ReactiveCache.getUser(userId); const user = Meteor.users.findOne(userId);
return user && user.isAdmin; return user && user.isAdmin;
}, },
}); });

View file

@ -78,8 +78,8 @@ Team.attachSchema(
if (Meteor.isServer) { if (Meteor.isServer) {
Team.allow({ Team.allow({
async insert(userId, doc) { insert(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -87,8 +87,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async update(userId, doc) { update(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -96,8 +96,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async remove(userId, doc) { remove(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {

View file

@ -58,8 +58,8 @@ Translation.attachSchema(
if (Meteor.isServer) { if (Meteor.isServer) {
Translation.allow({ Translation.allow({
async insert(userId, doc) { insert(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -67,8 +67,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async update(userId, doc) { update(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {
@ -76,8 +76,8 @@ if (Meteor.isServer) {
} }
return doc._id === userId; return doc._id === userId;
}, },
async remove(userId, doc) { remove(userId, doc) {
const user = await ReactiveCache.getUser(userId) || await ReactiveCache.getCurrentUser(); const user = Meteor.users.findOne(userId);
if (user?.isAdmin) if (user?.isAdmin)
return true; return true;
if (!user) { if (!user) {

View file

@ -14,14 +14,14 @@ Triggers.before.update((userId, doc, fieldNames, modifier) => {
}); });
Triggers.allow({ Triggers.allow({
async insert(userId, doc) { insert(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async update(userId, doc) { update(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
async remove(userId, doc) { remove(userId, doc) {
return allowIsBoardAdmin(userId, await ReactiveCache.getBoard(doc.boardId)); return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
}, },
}); });

View file

@ -1646,8 +1646,8 @@ class CronMigrationManager {
SyncedCron.add({ SyncedCron.add({
name: step.cronName, name: step.cronName,
schedule: (parser) => parser.text(step.schedule), schedule: (parser) => parser.text(step.schedule),
job: () => { job: async () => {
this.runMigrationStep(step); await this.runMigrationStep(step);
}, },
}); });
} }

View file

@ -27,12 +27,12 @@ allowIsBoardMemberWithWriteAccess = function(userId, board) {
// Check if user has write access via a card's board // Check if user has write access via a card's board
allowIsBoardMemberWithWriteAccessByCard = function(userId, card) { allowIsBoardMemberWithWriteAccessByCard = function(userId, card) {
const board = card && card.board && card.board(); const board = card && Boards.findOne(card.boardId);
return allowIsBoardMemberWithWriteAccess(userId, board); return allowIsBoardMemberWithWriteAccess(userId, board);
}; };
allowIsBoardMemberByCard = function(userId, card) { allowIsBoardMemberByCard = function(userId, card) {
const board = card.board(); const board = card && Boards.findOne(card.boardId);
return board && board.hasMember(userId); return board && board.hasMember(userId);
}; };

View file

@ -2,9 +2,9 @@ import { ReactiveCache } from '/imports/reactiveCache';
RulesHelper = { RulesHelper = {
async executeRules(activity) { async executeRules(activity) {
const matchingRules = this.findMatchingRules(activity); const matchingRules = await this.findMatchingRules(activity);
for (let i = 0; i < matchingRules.length; i++) { for (let i = 0; i < matchingRules.length; i++) {
const action = matchingRules[i].getAction(); const action = await matchingRules[i].getAction();
if (action !== undefined) { if (action !== undefined) {
await this.performAction(activity, action); await this.performAction(activity, action);
} }
@ -19,14 +19,14 @@ RulesHelper = {
const matchingMap = await this.buildMatchingFieldsMap(activity, matchingFields); const matchingMap = await this.buildMatchingFieldsMap(activity, matchingFields);
const matchingTriggers = await ReactiveCache.getTriggers(matchingMap); const matchingTriggers = await ReactiveCache.getTriggers(matchingMap);
const matchingRules = []; const matchingRules = [];
matchingTriggers.forEach(function(trigger) { for (const trigger of matchingTriggers) {
const rule = trigger.getRule(); const rule = await trigger.getRule();
// Check that for some unknown reason there are some leftover triggers // Check that for some unknown reason there are some leftover triggers
// not connected to any rules // not connected to any rules
if (rule !== undefined) { if (rule !== undefined) {
matchingRules.push(trigger.getRule()); matchingRules.push(rule);
} }
}); }
return matchingRules; return matchingRules;
}, },
async buildMatchingFieldsMap(activity, matchingFields) { async buildMatchingFieldsMap(activity, matchingFields) {
@ -67,7 +67,7 @@ RulesHelper = {
let list; let list;
let listId; let listId;
if (action.listName === '*') { if (action.listName === '*') {
list = card.list(); list = await card.list();
if (boardId !== action.boardId) { if (boardId !== action.boardId) {
list = await ReactiveCache.getList({ title: list.title, boardId: action.boardId }); list = await ReactiveCache.getList({ title: list.title, boardId: action.boardId });
} }
@ -110,12 +110,12 @@ RulesHelper = {
if (action.actionType === 'moveCardToTop') { if (action.actionType === 'moveCardToTop') {
const minOrder = _.min( const minOrder = _.min(
list.cardsUnfiltered(swimlaneId).map(c => c.sort), (await list.cardsUnfiltered(swimlaneId)).map(c => c.sort),
); );
await card.move(action.boardId, swimlaneId, listId, minOrder - 1); await card.move(action.boardId, swimlaneId, listId, minOrder - 1);
} else { } else {
const maxOrder = _.max( const maxOrder = _.max(
list.cardsUnfiltered(swimlaneId).map(c => c.sort), (await list.cardsUnfiltered(swimlaneId)).map(c => c.sort),
); );
await card.move(action.boardId, swimlaneId, listId, maxOrder + 1); await card.move(action.boardId, swimlaneId, listId, maxOrder + 1);
} }