mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42:50 +08:00
Merge PR #80: feat(perm) fine-grained account permissions
Conflict resolution + cross-PR integration: - player.ts: kept #88's POST /:botId/fm route AND gated it with requirePermission('player.control') so the new control endpoint honors #80's permission model (it was added without gating). - bot.ts: kept #81's relocated /settings routes (the relocation fixes the GET /settings shadow bug) and dropped #80's now-duplicate bottom copy; gated POST /settings with requirePermission('bot.manage'). - Navbar.vue: composed #82's dedicated-link scope with #80's permission filter — displayedBots is now the INTERSECTION (scope ∩ controllable allow-list). - database.ts: kept BOTH new table sets (#87 favorite_playlists + #80 user_permissions/user_bot_access). - bot.test.ts: updated to createRequireAuth(sessions, permissions) for #80's new two-arg signature. #80 review fixes (credential exposure / IDOR, adversarially verified): - GET /:id/config now requires bot.manage + bot access AND redacts ts6ApiKey + identity from the response (was readable by any authenticated member). - GET /:id and GET /:id/avatar now require bot access (were ungated read oracles).
This commit is contained in:
commit
bea2f92508
32 files changed
+2225
-137
No files matched your search
+25
-15
@@ -5,6 +5,7 @@ import { saveConfig } from "../../data/config.js";
|
||||
import type { Logger } from "../../logger.js";
|
||||
import type { BotDatabase } from "../../data/database.js";
|
||||
import type { AvatarStore } from "../../data/avatars.js";
|
||||
import { requirePermission, requireBotAccess } from "../middleware/requirePermission.js";
|
||||
|
||||
export function createBotRouter(
|
||||
botManager: BotManager,
|
||||
@@ -16,8 +17,13 @@ export function createBotRouter(
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
router.get("/", (_req, res) => {
|
||||
const bots = botManager.getAllBots().map((b) => b.getStatus());
|
||||
router.get("/", (req, res) => {
|
||||
const all = botManager.getAllBots().map((b) => b.getStatus());
|
||||
const u = req.user!;
|
||||
const bots =
|
||||
u.role === "admin" || u.bots === "all"
|
||||
? all
|
||||
: all.filter((b) => u.bots instanceof Set && u.bots.has(b.id));
|
||||
res.json({ bots });
|
||||
});
|
||||
|
||||
@@ -30,8 +36,9 @@ export function createBotRouter(
|
||||
});
|
||||
});
|
||||
|
||||
// POST /api/bot/settings — 保存全局 bot 行为设置
|
||||
router.post("/settings", (req, res) => {
|
||||
// POST /api/bot/settings — 保存全局 bot 行为设置 (gated: changing global bot
|
||||
// behavior is a bot.manage operation, consistent with PR #80's permission model)
|
||||
router.post("/settings", requirePermission("bot.manage"), (req, res) => {
|
||||
const { idleTimeoutMinutes, autoPauseOnEmpty } = req.body;
|
||||
|
||||
const hasIdle = idleTimeoutMinutes !== undefined;
|
||||
@@ -58,7 +65,7 @@ export function createBotRouter(
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/:id", (req, res) => {
|
||||
router.get("/:id", requireBotAccess("id"), (req, res) => {
|
||||
const bot = botManager.getBot(req.params.id);
|
||||
if (!bot) {
|
||||
res.status(404).json({ error: "Bot not found" });
|
||||
@@ -68,16 +75,19 @@ export function createBotRouter(
|
||||
});
|
||||
|
||||
// Get saved config for a bot
|
||||
router.get("/:id/config", (req, res) => {
|
||||
router.get("/:id/config", requirePermission("bot.manage"), requireBotAccess("id"), (req, res) => {
|
||||
const saved = botManager.getBotConfig(req.params.id);
|
||||
if (!saved) {
|
||||
res.status(404).json({ error: "Bot config not found" });
|
||||
return;
|
||||
}
|
||||
res.json(saved);
|
||||
// Never expose the TS identity / API key to the client; the edit form only
|
||||
// consumes channel/server passwords.
|
||||
const { ts6ApiKey: _ts6ApiKey, identity: _identity, ...safe } = saved as unknown as Record<string, unknown>;
|
||||
res.json(safe);
|
||||
});
|
||||
|
||||
router.get("/:id/avatar", (req, res) => {
|
||||
router.get("/:id/avatar", requirePermission("bot.manage"), requireBotAccess("id"), (req, res) => {
|
||||
const path = botDb.getCustomAvatarPath(req.params.id);
|
||||
if (!path) {
|
||||
res.status(404).end();
|
||||
@@ -99,7 +109,7 @@ export function createBotRouter(
|
||||
res.send(buf);
|
||||
});
|
||||
|
||||
router.put("/:id/avatar", (req, res) => {
|
||||
router.put("/:id/avatar", requirePermission("bot.manage"), requireBotAccess("id"), (req, res) => {
|
||||
const exists =
|
||||
botManager.getBot(req.params.id) ||
|
||||
botDb.getBotInstances().some((b) => b.id === req.params.id);
|
||||
@@ -133,7 +143,7 @@ export function createBotRouter(
|
||||
res.json({ path: rel });
|
||||
});
|
||||
|
||||
router.delete("/:id/avatar", (req, res) => {
|
||||
router.delete("/:id/avatar", requirePermission("bot.manage"), requireBotAccess("id"), (req, res) => {
|
||||
const path = botDb.getCustomAvatarPath(req.params.id);
|
||||
if (path) avatarStore.remove(path);
|
||||
botDb.setCustomAvatarPath(req.params.id, null);
|
||||
@@ -141,7 +151,7 @@ export function createBotRouter(
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
router.post("/", requirePermission("bot.manage"), async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
@@ -177,7 +187,7 @@ export function createBotRouter(
|
||||
});
|
||||
|
||||
// Update bot config (must be stopped first to apply connection changes)
|
||||
router.put("/:id", async (req, res) => {
|
||||
router.put("/:id", requirePermission("bot.manage"), requireBotAccess("id"), async (req, res) => {
|
||||
try {
|
||||
const bot = botManager.getBot(req.params.id);
|
||||
if (!bot) {
|
||||
@@ -196,7 +206,7 @@ export function createBotRouter(
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/:id", async (req, res) => {
|
||||
router.delete("/:id", requirePermission("bot.manage"), requireBotAccess("id"), async (req, res) => {
|
||||
try {
|
||||
await botManager.removeBot(req.params.id);
|
||||
res.json({ success: true });
|
||||
@@ -205,7 +215,7 @@ export function createBotRouter(
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/:id/start", async (req, res) => {
|
||||
router.post("/:id/start", requirePermission("bot.manage"), requireBotAccess("id"), async (req, res) => {
|
||||
try {
|
||||
await botManager.startBot(req.params.id);
|
||||
res.json({ success: true });
|
||||
@@ -214,7 +224,7 @@ export function createBotRouter(
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/:id/stop", (req, res) => {
|
||||
router.post("/:id/stop", requirePermission("bot.manage"), requireBotAccess("id"), (req, res) => {
|
||||
try {
|
||||
botManager.stopBot(req.params.id);
|
||||
res.json({ success: true });
|
||||
|
||||
Reference in new issue
Block a user