feat(perm): enforce capabilities + bot access on action routes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.8 committed 2026-05-30 13:32:08 +08:00
1 parent 696b224f8d
commit cd6f2c6078
6 files changed
+287 -39

No files matched your search

+24 -21
View File
@@ -4,6 +4,7 @@ import type { BotDatabase } from "../../data/database.js";
import type { MusicProvider } from "../../music/provider.js";
import type { Logger } from "../../logger.js";
import { parseCommand } from "../../bot/commands.js";
import { requirePermission, requireBotAccess } from "../middleware/requirePermission.js";
export function createPlayerRouter(
botManager: BotManager,
@@ -25,6 +26,8 @@ export function createPlayerRouter(
next();
});
router.use("/:botId", requireBotAccess("botId"));
/** Map API platform string to the corresponding command flag. */
const platformFlag = (platform: unknown): string => {
if (platform === "bilibili") return "-b";
@@ -33,7 +36,7 @@ export function createPlayerRouter(
return "";
};
router.post("/:botId/play", async (req, res) => {
router.post("/:botId/play", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { query, platform } = req.body;
@@ -53,7 +56,7 @@ export function createPlayerRouter(
}
});
router.post("/:botId/add", async (req, res) => {
router.post("/:botId/add", requirePermission("player.queue"), async (req, res) => {
try {
const bot = (req as any).bot;
const { query, platform } = req.body;
@@ -80,14 +83,14 @@ export function createPlayerRouter(
}
};
router.post("/:botId/pause", simpleCommand("!pause"));
router.post("/:botId/resume", simpleCommand("!resume"));
router.post("/:botId/next", simpleCommand("!next"));
router.post("/:botId/prev", simpleCommand("!prev"));
router.post("/:botId/stop", simpleCommand("!stop"));
router.post("/:botId/clear", simpleCommand("!clear"));
router.post("/:botId/pause", requirePermission("player.control"), simpleCommand("!pause"));
router.post("/:botId/resume", requirePermission("player.control"), simpleCommand("!resume"));
router.post("/:botId/next", requirePermission("player.control"), simpleCommand("!next"));
router.post("/:botId/prev", requirePermission("player.control"), simpleCommand("!prev"));
router.post("/:botId/stop", requirePermission("player.control"), simpleCommand("!stop"));
router.post("/:botId/clear", requirePermission("player.queue"), simpleCommand("!clear"));
router.post("/:botId/volume", async (req, res) => {
router.post("/:botId/volume", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { volume } = req.body;
@@ -115,7 +118,7 @@ export function createPlayerRouter(
const VALID_MODES = new Set(["seq", "loop", "random", "rloop"]);
router.post("/:botId/mode", async (req, res) => {
router.post("/:botId/mode", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { mode } = req.body;
@@ -140,7 +143,7 @@ export function createPlayerRouter(
});
// Seek to position
router.post("/:botId/seek", async (req, res) => {
router.post("/:botId/seek", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { position } = req.body; // seconds
@@ -164,7 +167,7 @@ export function createPlayerRouter(
res.json({ queue: bot.getQueue(), status: bot.getStatus() });
});
router.delete("/:botId/queue/:index", async (req, res) => {
router.delete("/:botId/queue/:index", requirePermission("player.queue"), async (req, res) => {
try {
const bot = (req as any).bot;
const cmd = parseCommand(`!remove ${req.params.index}`, "!")!;
@@ -176,7 +179,7 @@ export function createPlayerRouter(
});
// Jump to a specific index in the queue (without clearing it)
router.post("/:botId/play-at", async (req, res) => {
router.post("/:botId/play-at", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { index } = req.body;
@@ -210,7 +213,7 @@ export function createPlayerRouter(
}
});
router.post("/:botId/playlist", async (req, res) => {
router.post("/:botId/playlist", requirePermission("player.queue"), async (req, res) => {
try {
const bot = (req as any).bot;
const { playlistId, platform } = req.body;
@@ -227,7 +230,7 @@ export function createPlayerRouter(
// Play a playlist by ID — stores metadata only, resolves URL for first song
// Respects current play mode (random = pick random first song)
router.post("/:botId/play-playlist", async (req, res) => {
router.post("/:botId/play-playlist", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { playlistId, platform } = req.body;
@@ -314,7 +317,7 @@ export function createPlayerRouter(
});
// Play an album by ID — mirrors play-playlist but calls getAlbumSongs
router.post("/:botId/play-album", async (req, res) => {
router.post("/:botId/play-album", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { albumId, platform } = req.body;
@@ -386,7 +389,7 @@ export function createPlayerRouter(
});
// Play a single song by ID — resolves URL on demand
router.post("/:botId/play-song", async (req, res) => {
router.post("/:botId/play-song", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { song } = req.body;
@@ -414,7 +417,7 @@ export function createPlayerRouter(
// Insert a single song to play right after the current one.
// If nothing is playing, behaves like /play-song (start immediately).
router.post("/:botId/play-next-song", async (req, res) => {
router.post("/:botId/play-next-song", requirePermission("player.control"), async (req, res) => {
try {
const bot = (req as any).bot;
const { song } = req.body;
@@ -452,7 +455,7 @@ export function createPlayerRouter(
}
});
router.post("/:botId/add-song", async (req, res) => {
router.post("/:botId/add-song", requirePermission("player.queue"), async (req, res) => {
try {
const bot = (req as any).bot;
const { song } = req.body;
@@ -480,7 +483,7 @@ export function createPlayerRouter(
});
// Add a song to queue by ID — metadata only
router.post("/:botId/add-by-id", async (req, res) => {
router.post("/:botId/add-by-id", requirePermission("player.queue"), async (req, res) => {
try {
const bot = (req as any).bot;
const { songId, platform } = req.body;
@@ -518,7 +521,7 @@ export function createPlayerRouter(
res.json(bot.getProfileManager().getConfig());
});
router.put("/:botId/profile", (req, res) => {
router.put("/:botId/profile", requirePermission("bot.manage"), (req, res) => {
try {
const bot = (req as any).bot;
const pm = bot.getProfileManager();