mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 13:02:49 +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
@@ -22,8 +22,9 @@
|
||||
<button class="scope-exit-btn" @click="exitScope" title="退出专属模式">退出</button>
|
||||
</div>
|
||||
|
||||
<!-- Normal: full selector with switching (shown when at least one bot exists) -->
|
||||
<div v-else-if="store.bots.length > 0" class="bot-selector" ref="selectorRef">
|
||||
<!-- Normal: full selector with switching (shown when at least one
|
||||
controllable bot exists — scope ∩ permission via displayedBots) -->
|
||||
<div v-else-if="displayedBots.length > 0" class="bot-selector" ref="selectorRef">
|
||||
<button class="bot-selector-btn" @click="dropdownOpen = !dropdownOpen">
|
||||
<span class="bot-dot" :class="{ online: activeBot?.connected }" />
|
||||
<span class="bot-selector-name">{{ activeBot?.name ?? '选择机器人' }}</span>
|
||||
@@ -143,17 +144,26 @@ import { useSession } from '../composables/useSession.js';
|
||||
|
||||
const store = usePlayerStore();
|
||||
const session = useSession();
|
||||
const { canControlBot } = session;
|
||||
const navRouter = useRouter();
|
||||
|
||||
async function onLogout() {
|
||||
await session.logout();
|
||||
navRouter.replace({ name: 'login' });
|
||||
}
|
||||
// Belt-and-suspenders: the backend already scopes store.bots to the allowed
|
||||
// set for members, but filtering here keeps the UI correct if an admin (who
|
||||
// sees all bots) is constrained, or if the list ever isn't pre-filtered.
|
||||
const controllableBots = computed(() => store.bots.filter((b) => canControlBot(b.id)));
|
||||
const activeBot = computed(() => store.activeBot);
|
||||
// While scoped (dedicated link), the selector is locked to the single scoped
|
||||
// bot; otherwise the full list is shown and switching is allowed.
|
||||
// The bots shown in the selector are the INTERSECTION of the permission
|
||||
// allow-list (controllableBots) and the dedicated-link scope: while scoped the
|
||||
// selector is locked to the single scoped bot, otherwise the full controllable
|
||||
// list is shown and switching is allowed.
|
||||
const displayedBots = computed(() =>
|
||||
store.isScoped ? store.bots.filter((b) => b.id === store.scopedBotId) : store.bots,
|
||||
store.isScoped
|
||||
? controllableBots.value.filter((b) => b.id === store.scopedBotId)
|
||||
: controllableBots.value,
|
||||
);
|
||||
const dropdownOpen = ref(false);
|
||||
const selectorRef = ref<HTMLElement | null>(null);
|
||||
|
||||
Reference in new issue
Block a user