openvibers/openvibe.live Commits

Only the owner lifts a site-wide ban (#878)

OP OpenVibers committed 4 h ago · 056335fdb6 · 3 files, +33 −4

Changes

ddiff --git a/server/admin/mod-routes.js b/server/admin/mod-routes.js
iindex 1c201116..8c7360c9 100644
---- a/server/admin/mod-routes.js
++++ b/server/admin/mod-routes.js
@@@ -183,7 +183,8 @@ router.post('/users/:id/ban', permissions.requireGlobalMod, async (req, res) =>
}
});
--router.delete('/users/:id/ban', permissions.requireGlobalMod, async (req, res) => {
++// Lifting a site-wide ban is the owner's alone (the owner, 2026-11-13: "admins can't unban … only owner rank, but they can view").
++router.delete('/users/:id/ban', permissions.requireOwner, async (req, res) => {
try {
const userId = parseInt(req.params.id);
const targetUser = await db.getUserById(userId);
@@@ -290,8 +291,9 @@ router.delete('/ban/:id', async (req, res) => {
// Permission check: global bans require global_mod+, stream bans require stream mod
if (!ban.stream_id) {
-- if (!permissions.isGlobalModOrAbove(req.user)) {
-- return res.status(403).json({ error: 'Only global mods can remove global bans' });
++ // A site-wide ban is lifted by the owner only (2026-11-13); a stream's own ban stays with its moderators.
++ if (!permissions.isOwner(req.user)) {
++ return res.status(403).json({ error: 'Only the owner can lift a site-wide ban' });
}
// Clear is_banned flag if this was a global user ban
if (ban.user_id) {
ddiff --git a/server/admin/routes.js b/server/admin/routes.js
iindex 24bc2e38..c0be2224 100644
---- a/server/admin/routes.js
++++ b/server/admin/routes.js
@@@ -282,7 +282,8 @@ router.post('/users/:id/ban', async (req, res) => {
});
// ── Unban User ───────────────────────────────────────────────
--router.delete('/users/:id/ban', async (req, res) => {
++// Lifting a site-wide ban is the owner's alone (the owner, 2026-11-13: "admins can't unban … only owner rank, but they can view").
++router.delete('/users/:id/ban', permissions.requireOwner, async (req, res) => {
try {
await db.run('UPDATE users SET is_banned = 0, ban_reason = NULL WHERE id = ?', [req.params.id]);
await db.run('DELETE FROM bans WHERE user_id = ?', [req.params.id]);
ddiff --git a/test/unban-owner-only.test.js b/test/unban-owner-only.test.js
nnew file mode 100644
iindex 00000000..0aed19a5
---- /dev/null
++++ b/test/unban-owner-only.test.js
@@@ -0,0 +1,26 @@
++'use strict';
++// Lifting a site-wide ban is the owner's alone (the owner, 2026-11-13: "admins can't unban … only owner rank, but they
++// can view"). Every route that clears a site ban checks for the owner; a stream's own ban stays with its moderators.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const read = (p) => fs.readFileSync(path.join(__dirname, '..', p), 'utf8');
++
++const mod = read('server/admin/mod-routes.js');
++const admin = read('server/admin/routes.js');
++assert.ok(/router\.delete\('\/users\/:id\/ban', permissions\.requireOwner, async/.test(mod), 'mod routes: DELETE /users/:id/ban is the owner\'s');
++assert.ok(/router\.delete\('\/users\/:id\/ban', permissions\.requireOwner, async/.test(admin), 'admin routes: DELETE /users/:id/ban is the owner\'s');
++assert.ok(/if \(!ban\.stream_id\) \{[\s\S]{0,200}if \(!permissions\.isOwner\(req\.user\)\) \{\s*return res\.status\(403\)/.test(mod), 'DELETE /ban/:id: a site-wide ban needs the owner');
++assert.ok(!/router\.delete\('\/users\/:id\/ban', permissions\.requireGlobalMod/.test(mod), 'no global mod path left');
++
++const permissions = require('../server/auth/permissions');
++const run = (user) => {
++ let status = 200, passed = false;
++ const res = { status(s) { status = s; return this; }, json() { return this; } };
++ permissions.requireOwner({ user }, res, () => { passed = true; });
++ return { status, passed };
++};
++assert.strictEqual(run({ id: 1, role: 'admin', is_owner: 0 }).passed, false, 'an admin who is not the owner is turned away');
++assert.strictEqual(run({ id: 2, role: 'global_mod' }).passed, false, 'a global mod is turned away');
++assert.strictEqual(run({ id: 3, role: 'admin', is_owner: 1 }).passed, true, 'the owner may lift it');
++console.log('unban-owner-only: only the owner lifts a site-wide ban; admins and mods see the list');