feat: implement board access control (#349)

* feat: implement board access control

* fix: deepsource issues

* wip: address pull request feedback

* chore: address pull request feedback

* fix: format issue

* test: improve tests

* fix: type and lint issue

* chore: address pull request feedback

* refactor: rename board procedures
This commit is contained in:
Meier Lukas
2024-04-30 21:32:55 +02:00
committed by GitHub
parent 56388eb8ef
commit 7ab9dc2501
50 changed files with 1020 additions and 324 deletions

View File

@@ -0,0 +1,188 @@
import { describe, expect, test, vi } from "vitest";
import * as authShared from "@homarr/auth/shared";
import { createId, eq } from "@homarr/db";
import { boards, users } from "@homarr/db/schema/sqlite";
import { createDb } from "@homarr/db/test";
import { throwIfActionForbiddenAsync } from "../../board/board-access";
const defaultCreatorId = createId();
const expectActToBe = async (act: () => Promise<void>, success: boolean) => {
if (!success) {
await expect(act()).rejects.toThrow("Board not found");
return;
}
await expect(act()).resolves.toBeUndefined();
};
// TODO: most of this test can be used for constructBoardPermissions
// TODO: the tests for the board-access can be reduced to about 4 tests (as the unit has shrunk)
describe("throwIfActionForbiddenAsync should check access to board and return boolean", () => {
test.each([
["full-access" as const, true],
["board-change" as const, true],
["board-view" as const, true],
])(
"with permission %s should return %s when hasFullAccess is true",
async (permission, expectedResult) => {
// Arrange
const db = createDb();
const spy = vi.spyOn(authShared, "constructBoardPermissions");
spy.mockReturnValue({
hasFullAccess: true,
hasChangeAccess: false,
hasViewAccess: false,
});
await db.insert(users).values({ id: defaultCreatorId });
const boardId = createId();
await db.insert(boards).values({
id: boardId,
name: "test",
creatorId: defaultCreatorId,
});
// Act
const act = () =>
throwIfActionForbiddenAsync(
{ db, session: null },
eq(boards.id, boardId),
permission,
);
// Assert
await expectActToBe(act, expectedResult);
},
);
test.each([
["full-access" as const, false],
["board-change" as const, true],
["board-view" as const, true],
])(
"with permission %s should return %s when hasChangeAccess is true",
async (permission, expectedResult) => {
// Arrange
const db = createDb();
const spy = vi.spyOn(authShared, "constructBoardPermissions");
spy.mockReturnValue({
hasFullAccess: false,
hasChangeAccess: true,
hasViewAccess: false,
});
await db.insert(users).values({ id: defaultCreatorId });
const boardId = createId();
await db.insert(boards).values({
id: boardId,
name: "test",
creatorId: defaultCreatorId,
});
// Act
const act = () =>
throwIfActionForbiddenAsync(
{ db, session: null },
eq(boards.id, boardId),
permission,
);
// Assert
await expectActToBe(act, expectedResult);
},
);
test.each([
["full-access" as const, false],
["board-change" as const, false],
["board-view" as const, true],
])(
"with permission %s should return %s when hasViewAccess is true",
async (permission, expectedResult) => {
// Arrange
const db = createDb();
const spy = vi.spyOn(authShared, "constructBoardPermissions");
spy.mockReturnValue({
hasFullAccess: false,
hasChangeAccess: false,
hasViewAccess: true,
});
await db.insert(users).values({ id: defaultCreatorId });
const boardId = createId();
await db.insert(boards).values({
id: boardId,
name: "test",
creatorId: defaultCreatorId,
});
// Act
const act = () =>
throwIfActionForbiddenAsync(
{ db, session: null },
eq(boards.id, boardId),
permission,
);
// Assert
await expectActToBe(act, expectedResult);
},
);
test.each([
["full-access" as const, false],
["board-change" as const, false],
["board-view" as const, false],
])(
"with permission %s should return %s when hasViewAccess is false",
async (permission, expectedResult) => {
// Arrange
const db = createDb();
const spy = vi.spyOn(authShared, "constructBoardPermissions");
spy.mockReturnValue({
hasFullAccess: false,
hasChangeAccess: false,
hasViewAccess: false,
});
await db.insert(users).values({ id: defaultCreatorId });
const boardId = createId();
await db.insert(boards).values({
id: boardId,
name: "test",
creatorId: defaultCreatorId,
});
// Act
const act = () =>
throwIfActionForbiddenAsync(
{ db, session: null },
eq(boards.id, boardId),
permission,
);
// Assert
await expectActToBe(act, expectedResult);
},
);
test("should throw when board is not found", async () => {
// Arrange
const db = createDb();
// Act
const act = () =>
throwIfActionForbiddenAsync(
{ db, session: null },
eq(boards.id, createId()),
"full-access",
);
// Assert
await expect(act()).rejects.toThrow("Board not found");
});
});