mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix(guest): guard reserved __guest__ principal in user mgmt
The by-id user-management handlers use findById, which has no role filter, so supplying the synthetic GUEST_USER_ID let an admin delete, re-role, reset-password, and read/write permissions on the shared guest principal (privilege-escalation / DoS / credential-login holes). - web/api/users.ts: 404-guard every :id handler against GUEST_USER_ID (DELETE, reset-password, role, GET/PUT permissions). - data/users.ts: defense-in-depth — setRoleIfNotLastAdmin and deleteUserIfNotLastAdmin return "not_found" for any role=guest row. - web/api/session.ts: wrap POST /guest createSession in try/catch so a missing guest row yields 503 instead of an unhandled 500. - Tests: data-layer guest-protection + users-router 404 by-id guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45414b3baa
commit
365352cdd3
5 files changed
+79
-3
No files matched your search
@@ -4,7 +4,7 @@ import cookieParser from "cookie-parser";
|
||||
import request from "supertest";
|
||||
import pino from "pino";
|
||||
import { createDatabase, type BotDatabase } from "../../data/database.js";
|
||||
import { createUserStore, type UserStore } from "../../data/users.js";
|
||||
import { createUserStore, GUEST_USER_ID, type UserStore } from "../../data/users.js";
|
||||
import { createSessionStore, type SessionStore } from "../../data/sessions.js";
|
||||
import { createAuditStore, type AuditStore } from "../../data/audit.js";
|
||||
import { createPermissionStore, type PermissionStore } from "../../data/permissions.js";
|
||||
@@ -342,4 +342,49 @@ describe("users router", () => {
|
||||
expect(perms.status).toBe(200);
|
||||
expect(perms.body).toEqual({ capabilities: [], bots: [] });
|
||||
});
|
||||
|
||||
// --- reserved guest principal is never mutable/visible via user mgmt -------
|
||||
// The synthetic __guest__ row is seeded by createDatabase. findById has no
|
||||
// role filter, so without the 404-guard these by-id handlers would operate
|
||||
// on it (privilege-escalation / DoS / cred-login holes).
|
||||
describe("reserved __guest__ principal is 404 on every by-id handler", () => {
|
||||
it("DELETE /:id → 404 and the guest row survives", async () => {
|
||||
const res = await request(app).delete(`/api/users/${GUEST_USER_ID}`).set("Cookie", aliceCookie);
|
||||
expect(res.status).toBe(404);
|
||||
expect(users.findById(GUEST_USER_ID)).not.toBeNull();
|
||||
expect(users.findById(GUEST_USER_ID)!.role).toBe("guest");
|
||||
});
|
||||
|
||||
it("PATCH /:id/role {role:'admin'} → 404 and the guest role is unchanged", async () => {
|
||||
const res = await request(app)
|
||||
.patch(`/api/users/${GUEST_USER_ID}/role`)
|
||||
.set("Cookie", aliceCookie)
|
||||
.send({ role: "admin" });
|
||||
expect(res.status).toBe(404);
|
||||
expect(users.findById(GUEST_USER_ID)!.role).toBe("guest");
|
||||
});
|
||||
|
||||
it("POST /:id/reset-password → 404 (cannot give the guest a login)", async () => {
|
||||
const res = await request(app)
|
||||
.post(`/api/users/${GUEST_USER_ID}/reset-password`)
|
||||
.set("Cookie", aliceCookie)
|
||||
.send({ newPassword: "guest-new-pw" });
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it("GET /:id/permissions → 404", async () => {
|
||||
const res = await request(app)
|
||||
.get(`/api/users/${GUEST_USER_ID}/permissions`)
|
||||
.set("Cookie", aliceCookie);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it("PUT /:id/permissions → 404", async () => {
|
||||
const res = await request(app)
|
||||
.put(`/api/users/${GUEST_USER_ID}/permissions`)
|
||||
.set("Cookie", aliceCookie)
|
||||
.send({ capabilities: ["player.control"], bots: "all" });
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user