Ver Fonte

RHL-004-feat(auth): implement role-based access control functions and tests for branch access

Code_Uwe há 2 dias atrás
pai
commit
4dd01736ea
2 ficheiros alterados com 139 adições e 0 exclusões
  1. 62 0
      lib/auth/permissions.js
  2. 77 0
      lib/auth/permissions.test.js

+ 62 - 0
lib/auth/permissions.js

@@ -0,0 +1,62 @@
+/**
+ * Role-Based Access Control (RBAC) helpers.
+ *
+ * @typedef {Object} Session
+ * @property {"branch"|"admin"|"dev"|string} role
+ * @property {string=} branchId
+ */
+
+/**
+ * Returns true if the given session is allowed to access the requested branch.
+ *
+ * Rules:
+ * - No session => not allowed (caller should return 401)
+ * - role "branch" => allowed only for session.branchId === branchId
+ * - role "admin" / "dev" => allowed for any branch
+ *
+ * @param {Session|null} session
+ * @param {string} branchId
+ * @returns {boolean}
+ */
+export function canAccessBranch(session, branchId) {
+	if (!session) return false;
+	if (!branchId) return false;
+
+	if (session.role === "branch") {
+		return session.branchId === branchId;
+	}
+
+	if (session.role === "admin" || session.role === "dev") {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Filters a list of branch IDs for a session.
+ *
+ * - No session => []
+ * - role "branch" => [session.branchId] if present in branchIds, else []
+ * - role "admin" / "dev" => all branchIds
+ *
+ * @param {Session|null} session
+ * @param {string[]} branchIds
+ * @returns {string[]}
+ */
+export function filterBranchesForSession(session, branchIds) {
+	if (!session) return [];
+	if (!Array.isArray(branchIds)) return [];
+
+	if (session.role === "branch") {
+		const own = session.branchId;
+		if (!own) return [];
+		return branchIds.includes(own) ? [own] : [];
+	}
+
+	if (session.role === "admin" || session.role === "dev") {
+		return branchIds;
+	}
+
+	return [];
+}

+ 77 - 0
lib/auth/permissions.test.js

@@ -0,0 +1,77 @@
+/* @vitest-environment node */
+
+import { describe, it, expect } from "vitest";
+import { canAccessBranch, filterBranchesForSession } from "./permissions.js";
+
+describe("lib/auth/permissions", () => {
+	describe("canAccessBranch", () => {
+		it("returns false when session is missing", () => {
+			expect(canAccessBranch(null, "NL01")).toBe(false);
+		});
+
+		it("returns false when branchId is missing", () => {
+			expect(canAccessBranch({ role: "admin" }, "")).toBe(false);
+			expect(canAccessBranch({ role: "admin" }, null)).toBe(false);
+			expect(canAccessBranch({ role: "admin" }, undefined)).toBe(false);
+		});
+
+		it("allows branch role only for its own branch", () => {
+			const session = { role: "branch", branchId: "NL01" };
+			expect(canAccessBranch(session, "NL01")).toBe(true);
+			expect(canAccessBranch(session, "NL02")).toBe(false);
+		});
+
+		it("allows admin role for any branch", () => {
+			const session = { role: "admin" };
+			expect(canAccessBranch(session, "NL01")).toBe(true);
+			expect(canAccessBranch(session, "NL99")).toBe(true);
+		});
+
+		it("allows dev role for any branch", () => {
+			const session = { role: "dev" };
+			expect(canAccessBranch(session, "NL01")).toBe(true);
+			expect(canAccessBranch(session, "NL99")).toBe(true);
+		});
+
+		it("denies unknown roles", () => {
+			const session = { role: "user", branchId: "NL01" };
+			expect(canAccessBranch(session, "NL01")).toBe(false);
+		});
+	});
+
+	describe("filterBranchesForSession", () => {
+		it("returns [] when session is missing", () => {
+			expect(filterBranchesForSession(null, ["NL01", "NL02"])).toEqual([]);
+		});
+
+		it("returns [] when branchIds is not an array", () => {
+			expect(filterBranchesForSession({ role: "admin" }, null)).toEqual([]);
+			expect(filterBranchesForSession({ role: "admin" }, "NL01")).toEqual([]);
+		});
+
+		it("for branch role: returns only own branch (if present)", () => {
+			const session = { role: "branch", branchId: "NL01" };
+			expect(filterBranchesForSession(session, ["NL01", "NL02"])).toEqual([
+				"NL01",
+			]);
+			expect(filterBranchesForSession(session, ["NL02", "NL03"])).toEqual([]);
+		});
+
+		it("for admin/dev: returns the full list", () => {
+			const branches = ["NL01", "NL02", "NL03"];
+			expect(filterBranchesForSession({ role: "admin" }, branches)).toEqual(
+				branches
+			);
+			expect(filterBranchesForSession({ role: "dev" }, branches)).toEqual(
+				branches
+			);
+		});
+
+		it("does not mutate the input array", () => {
+			const branches = ["NL01", "NL02"];
+			const copy = [...branches];
+			filterBranchesForSession({ role: "admin" }, branches);
+			expect(branches).toEqual(copy);
+		});
+	});
+});