diff --git a/artifacts/api-server/src/routes/executive-meetings.ts b/artifacts/api-server/src/routes/executive-meetings.ts index e3d2bc44..99c4c8d4 100644 --- a/artifacts/api-server/src/routes/executive-meetings.ts +++ b/artifacts/api-server/src/routes/executive-meetings.ts @@ -909,9 +909,21 @@ router.get( const mine = req.query.mine === "true" || req.query.mine === "1"; const userId = req.session.userId!; + // SERVER-SIDE LEAST-PRIVILEGE: only approver/admin/audit roles can see + // requests submitted by other users. Everyone else (CEO, coordinators, + // viewers) is silently scoped to their own submissions regardless of + // what the client asked for. The client may still pass mine=1 to + // narrow further, but it cannot escape the requester filter. + const names = await getRoleNamesForUser(userId); + const canSeeAllRequests = + APPROVE_ROLES.some((r) => names.has(r)) || + ADMIN_AUDIT_ROLES.some((r) => names.has(r)); + const conds: SQL[] = []; if (status) conds.push(eq(executiveMeetingRequestsTable.status, status)); - if (mine) conds.push(eq(executiveMeetingRequestsTable.requestedBy, userId)); + if (mine || !canSeeAllRequests) { + conds.push(eq(executiveMeetingRequestsTable.requestedBy, userId)); + } const where = conds.length > 0 ? and(...conds) : undefined; const rows = await db diff --git a/artifacts/tx-os/src/locales/ar.json b/artifacts/tx-os/src/locales/ar.json index 7db5085d..806bc885 100644 --- a/artifacts/tx-os/src/locales/ar.json +++ b/artifacts/tx-os/src/locales/ar.json @@ -732,6 +732,11 @@ "edit": "تعديل بيانات", "delete": "حذف", "reschedule": "إعادة جدولة", + "add_attendee": "إضافة حاضر", + "remove_attendee": "إزالة حاضر", + "change_location": "تغيير المكان", + "cancel_meeting": "إلغاء الاجتماع", + "note": "ملاحظة", "highlight": "تمييز", "unhighlight": "إزالة التمييز", "other": "آخر" @@ -740,7 +745,9 @@ "new": "جديد", "approved": "تمت الموافقة", "rejected": "مرفوض", - "withdrawn": "مسحوب" + "needs_edit": "يحتاج تعديل", + "withdrawn": "مسحوب", + "done": "تم" } }, "approvals": { diff --git a/artifacts/tx-os/src/locales/en.json b/artifacts/tx-os/src/locales/en.json index 0ee0d4de..d687f02e 100644 --- a/artifacts/tx-os/src/locales/en.json +++ b/artifacts/tx-os/src/locales/en.json @@ -729,6 +729,11 @@ "edit": "Edit details", "delete": "Delete", "reschedule": "Reschedule", + "add_attendee": "Add attendee", + "remove_attendee": "Remove attendee", + "change_location": "Change location", + "cancel_meeting": "Cancel meeting", + "note": "Note", "highlight": "Highlight", "unhighlight": "Remove highlight", "other": "Other" @@ -737,7 +742,9 @@ "new": "New", "approved": "Approved", "rejected": "Rejected", - "withdrawn": "Withdrawn" + "needs_edit": "Needs edit", + "withdrawn": "Withdrawn", + "done": "Done" } }, "approvals": { diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index f3961e1c..fd3a24d5 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -224,7 +224,10 @@ function isSectionVisible( case "approvals": return me.canApprove; case "tasks": - return me.canMutate || me.canApprove; + // Coordination team (executive_coordinator) needs the Tasks tab even + // though they don't have full mutation rights — they execute follow-ups + // generated from approved requests. canSubmitRequest covers them. + return me.canMutate || me.canApprove || me.canSubmitRequest; case "notifications": return me.canRead; case "audit": @@ -1452,6 +1455,11 @@ function RequestsSection({ "edit", "delete", "reschedule", + "add_attendee", + "remove_attendee", + "change_location", + "cancel_meeting", + "note", "highlight", "unhighlight", "other", @@ -1474,7 +1482,7 @@ function RequestsSection({ {form.requestType === "reschedule" && ( <> - +