kanban-dev-webapp/tests/integration/user-rules.test.ts

79 lines
2.8 KiB
TypeScript
Raw Normal View History

import { describe, it, expect, beforeAll } from "vitest";
import PocketBase from "pocketbase";
import { DEMO, PB_URL, adminPb, requireServices, userPb } from "./helpers";
/**
* Regression guard for a live privilege escalation.
*
* The built-in `users` collection ships with createRule "" (anyone may sign
* up) and updateRule "id = @request.auth.id" (anyone may edit themselves).
* Because `role` is an ordinary field on that collection, both were
* exploitable: an anonymous request could create an owner account, and a
* client manager could promote themselves to owner and then read every other
* client's board. Both were verified working against the live instance before
* the rules were locked.
*
* If either test here fails, someone has reverted the users rules and every
* client's data is readable by anyone who can reach the PocketBase URL.
*/
let admin: PocketBase;
beforeAll(async () => {
await requireServices();
admin = await adminPb();
});
describe("the users collection cannot be used to escalate privileges", () => {
it("refuses anonymous account creation", async () => {
const anon = new PocketBase(PB_URL);
anon.autoCancellation(false);
await expect(
anon.collection("users").create({
email: `anon-${Date.now()}@example.com`,
password: "probe-password-123",
passwordConfirm: "probe-password-123",
name: "Should not exist",
role: "owner",
})
).rejects.toThrow();
});
it("refuses a client manager promoting themselves to owner", async () => {
const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword);
const selfId = pb.authStore.record!.id;
await expect(
pb.collection("users").update(selfId, { role: "owner" })
).rejects.toThrow();
const after = await admin.collection("users").getOne(selfId);
expect(after.role).toBe("client_manager");
});
it("refuses a client manager reassigning themselves to another client", async () => {
// The other half of the same hole: switching `client` would hand them a
// different customer's entire board through the normal read rules.
const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword);
const selfId = pb.authStore.record!.id;
const before = await admin.collection("users").getOne(selfId);
await expect(
pb.collection("users").update(selfId, { client: "" })
).rejects.toThrow();
const after = await admin.collection("users").getOne(selfId);
expect(after.client).toBe(before.client);
});
it("does not let a client manager list other accounts", async () => {
const pb = await userPb(DEMO.managerEmail, DEMO.managerPassword);
const visible = await pb.collection("users").getFullList();
expect(visible).toHaveLength(1);
expect(visible[0].id).toBe(pb.authStore.record!.id);
});
});