mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-01 20:42: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
@@ -208,4 +208,19 @@ describe("guest row exclusion", () => {
|
||||
expect(users.countUsers()).toBe(1); // alice only
|
||||
expect(users.listUsers().some((u) => u.id === GUEST_USER_ID)).toBe(false);
|
||||
});
|
||||
|
||||
it("setRoleIfNotLastAdmin refuses to re-role the reserved guest principal", () => {
|
||||
// The guest row is seeded by createDatabase via ensureGuestUser.
|
||||
expect(users.findById(GUEST_USER_ID)!.role).toBe("guest"); // sanity
|
||||
expect(users.setRoleIfNotLastAdmin(GUEST_USER_ID, "admin")).toBe("not_found");
|
||||
// The guest row's role is unchanged.
|
||||
expect(users.findById(GUEST_USER_ID)!.role).toBe("guest");
|
||||
});
|
||||
|
||||
it("deleteUserIfNotLastAdmin refuses to delete the reserved guest principal", () => {
|
||||
expect(users.findById(GUEST_USER_ID)).not.toBeNull(); // sanity
|
||||
expect(users.deleteUserIfNotLastAdmin(GUEST_USER_ID)).toBe("not_found");
|
||||
// The guest row still exists.
|
||||
expect(users.findById(GUEST_USER_ID)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -137,6 +137,7 @@ export function createUserStore(db: Database.Database): UserStore {
|
||||
const tx = db.transaction(() => {
|
||||
const row = findByIdStmt.get(id) as UserRow | undefined;
|
||||
if (!row) return "not_found" as const;
|
||||
if (row.role === "guest") return "not_found" as const; // reserved synthetic principal
|
||||
if (row.role === newRole) return "ok" as const; // no-op
|
||||
if (row.role === "admin" && newRole === "member") {
|
||||
const adminCount = (countAdminsStmt.get() as { n: number }).n;
|
||||
@@ -161,6 +162,7 @@ export function createUserStore(db: Database.Database): UserStore {
|
||||
const tx = db.transaction(() => {
|
||||
const row = findByIdStmt.get(id) as UserRow | undefined;
|
||||
if (!row) return "not_found" as const;
|
||||
if (row.role === "guest") return "not_found" as const; // reserved synthetic principal
|
||||
if (row.role === "admin") {
|
||||
const adminCount = (countAdminsStmt.get() as { n: number }).n;
|
||||
if (adminCount <= 1) return "would_orphan" as const;
|
||||
|
||||
Reference in new issue
Block a user