fix: permissions not restricted for certain management pages / actions (#1219)

* fix: restrict parts of manage navigation to admins

* fix: restrict stats cards on manage home page

* fix: restrict access to amount of certain stats for manage home

* fix: restrict visibility of board create button

* fix: restrict access to integration pages

* fix: restrict access to tools pages for admins

* fix: restrict access to user and group pages

* test: adjust tests to match permission changes for routes

* fix: remove certain pages from spotlight without admin

* fix: app management not restricted
This commit is contained in:
Meier Lukas
2024-10-05 17:03:32 +02:00
committed by GitHub
parent 770768eb21
commit 1421ccc917
28 changed files with 756 additions and 322 deletions

View File

@@ -12,15 +12,17 @@ export interface IntegrationPermissionsProps {
}
export const constructIntegrationPermissions = (integration: IntegrationPermissionsProps, session: Session | null) => {
const permissions = integration.userPermissions
.concat(integration.groupPermissions)
.map(({ permission }) => permission);
return {
hasFullAccess: session?.user.permissions.includes("integration-full-all") ?? false,
hasFullAccess:
(session?.user.permissions.includes("integration-full-all") ?? false) || permissions.includes("full"),
hasInteractAccess:
integration.userPermissions.some(({ permission }) => permission === "interact") ||
integration.groupPermissions.some(({ permission }) => permission === "interact") ||
permissions.includes("full") ||
permissions.includes("interact") ||
(session?.user.permissions.includes("integration-interact-all") ?? false),
hasUseAccess:
integration.userPermissions.length >= 1 ||
integration.groupPermissions.length >= 1 ||
(session?.user.permissions.includes("integration-use-all") ?? false),
hasUseAccess: permissions.length >= 1 || (session?.user.permissions.includes("integration-use-all") ?? false),
};
};

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
import { cookies } from "next/headers";
import type { Adapter, AdapterUser } from "@auth/core/adapters";
import type { Account } from "next-auth";
@@ -13,6 +11,14 @@ import * as definitions from "@homarr/definitions";
import { createSessionCallback, createSignInCallback, getCurrentUserPermissionsAsync } from "../callbacks";
// This one is placed here because it's used in multiple tests and needs to be the same reference
const setCookies = vi.fn();
vi.mock("next/headers", () => ({
cookies: () => ({
set: setCookies,
}),
}));
describe("getCurrentUserPermissions", () => {
test("should return empty permissions when non existing user requested", async () => {
// Arrange
@@ -170,21 +176,6 @@ vi.mock("../session", async (importOriginal) => {
expireDateAfter,
} satisfies SessionExport;
});
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type HeadersExport = typeof import("next/headers");
vi.mock("next/headers", async (importOriginal) => {
const mod = await importOriginal<HeadersExport>();
const result = {
set: (name: string, value: string, options: Partial<ResponseCookie>) => options as ResponseCookie,
} as unknown as ReadonlyRequestCookies;
vi.spyOn(result, "set");
const cookies = () => result;
return { ...mod, cookies } satisfies HeadersExport;
});
describe("createSignInCallback", () => {
test("should return true if not credentials request and set colorScheme & sessionToken cookie", async () => {
@@ -232,7 +223,6 @@ describe("createSignInCallback", () => {
const signInCallback = createSignInCallback(adapter, db, isCredentialsRequest);
const user = { id: "1", emailVerified: new Date("2023-01-13") };
const account = {} as Account;
// Act
await signInCallback({ user, account });
@@ -253,7 +243,7 @@ describe("createSignInCallback", () => {
test("should set colorScheme from db as cookie", async () => {
// Arrange
const isCredentialsRequest = false;
const isCredentialsRequest = true;
const db = await prepareDbForSigninAsync("1");
const signInCallback = createSignInCallback(createAdapter(), db, isCredentialsRequest);