From de5912c206a1e4abee380b886fe5b1cac5c856dc Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Mon, 16 Mar 2026 18:29:51 -0400 Subject: [PATCH] v0.9.39 bugs fixes --- .env.example | 2 +- backend/package.json | 2 +- backend/src/middleware/auth.js | 20 +++++++++++++++++++- backend/src/routes/usergroups.js | 16 +++++++--------- build.sh | 2 +- frontend/package.json | 2 +- 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 92b3ffd..7c46753 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,7 @@ PROJECT_NAME=jama # Image version to run (set by build.sh, or use 'latest') -JAMA_VERSION=0.9.38 +JAMA_VERSION=0.9.40 # App port — the host port Docker maps to the container PORT=3000 diff --git a/backend/package.json b/backend/package.json index 75d6d06..79ad20c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "jama-backend", - "version": "0.9.38", + "version": "0.9.40", "description": "TeamChat backend server", "main": "src/index.js", "scripts": { diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js index a464b25..94a649c 100644 --- a/backend/src/middleware/auth.js +++ b/backend/src/middleware/auth.js @@ -43,6 +43,24 @@ function adminMiddleware(req, res, next) { next(); } +// Allows admins OR members of groups designated as Group Managers or Schedule Managers +function teamManagerMiddleware(req, res, next) { + if (req.user?.role === 'admin') return next(); + const db = getDb(); + const gmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_group_managers'").get(); + const smSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_schedule_managers'").get(); + const allowedGroupIds = [ + ...JSON.parse(gmSetting?.value || '[]'), + ...JSON.parse(smSetting?.value || '[]'), + ]; + if (allowedGroupIds.length === 0) return res.status(403).json({ error: 'Access denied' }); + const member = db.prepare(` + SELECT 1 FROM user_group_members WHERE user_id = ? AND user_group_id IN (${allowedGroupIds.map(() => '?').join(',')}) + `).get(req.user.id, ...allowedGroupIds); + if (!member) return res.status(403).json({ error: 'Access denied' }); + next(); +} + function generateToken(userId) { return jwt.sign({ id: userId }, JWT_SECRET, { expiresIn: '30d' }); } @@ -70,4 +88,4 @@ function clearActiveSession(userId, device) { } } -module.exports = { authMiddleware, adminMiddleware, generateToken, setActiveSession, clearActiveSession, getDeviceClass }; +module.exports = { authMiddleware, adminMiddleware, teamManagerMiddleware, generateToken, setActiveSession, clearActiveSession, getDeviceClass }; diff --git a/backend/src/routes/usergroups.js b/backend/src/routes/usergroups.js index c2ce0c9..0a99280 100644 --- a/backend/src/routes/usergroups.js +++ b/backend/src/routes/usergroups.js @@ -1,7 +1,7 @@ const express = require('express'); const router = express.Router(); const { getDb } = require('../models/db'); -const { authMiddleware, adminMiddleware } = require('../middleware/auth'); +const { authMiddleware, adminMiddleware, teamManagerMiddleware } = require('../middleware/auth'); module.exports = function(io) { @@ -55,7 +55,7 @@ router.get('/me', authMiddleware, (req, res) => { // ── MULTI-GROUP DMs — must come before /:id ─────────────────────────────────── -router.get('/multigroup', authMiddleware, adminMiddleware, (req, res) => { +router.get('/multigroup', authMiddleware, teamManagerMiddleware, (req, res) => { const db = getDb(); const dms = db.prepare(` SELECT mgd.*, @@ -166,7 +166,7 @@ router.delete('/multigroup/:id', authMiddleware, adminMiddleware, (req, res) => // ── USER GROUPS ─────────────────────────────────────────────────────────────── -router.get('/', authMiddleware, adminMiddleware, (req, res) => { +router.get('/', authMiddleware, teamManagerMiddleware, (req, res) => { const db = getDb(); const groups = db.prepare(` SELECT ug.*, @@ -176,7 +176,7 @@ router.get('/', authMiddleware, adminMiddleware, (req, res) => { res.json({ groups }); }); -router.get('/:id', authMiddleware, adminMiddleware, (req, res) => { +router.get('/:id', authMiddleware, teamManagerMiddleware, (req, res) => { const db = getDb(); const group = db.prepare('SELECT * FROM user_groups WHERE id = ?').get(req.params.id); if (!group) return res.status(404).json({ error: 'Not found' }); @@ -252,11 +252,9 @@ router.patch('/:id', authMiddleware, adminMiddleware, (req, res) => { for (const uid of currentSet) { if (!newIds.has(uid)) { db.prepare('DELETE FROM user_group_members WHERE user_group_id = ? AND user_id = ?').run(ug.id, uid); - const stillHasAccess = db.prepare(`SELECT 1 FROM user_group_members ugm WHERE ugm.user_id = ? AND ugm.user_group_id != ? AND EXISTS (SELECT 1 FROM group_members gm WHERE gm.group_id = ? AND gm.user_id = ?)`).get(uid, ug.id, ug.dm_group_id, uid); - if (!stillHasAccess) { - removeUser(db, ug.dm_group_id, uid, req.user.id); - removedUids.push(uid); - } + // For managed DMs, membership is controlled solely by the user group — always remove + removeUser(db, ug.dm_group_id, uid, req.user.id); + removedUids.push(uid); } } diff --git a/build.sh b/build.sh index 88a0d58..bd0f484 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.9.38}" +VERSION="${1:-0.9.40}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="jama" diff --git a/frontend/package.json b/frontend/package.json index 8c698cd..ae4d84b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jama-frontend", - "version": "0.9.38", + "version": "0.9.40", "private": true, "scripts": { "dev": "vite",