diff --git a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx index 06255e0ff..e5d634e33 100644 --- a/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx +++ b/apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx @@ -66,9 +66,13 @@ const getBoardAndPermissionsAsync = async (params: Props["params"]) => { export default async function BoardSettingsPage({ params, searchParams }: Props) { const { board, permissions } = await getBoardAndPermissionsAsync(params); const boardSettings = await getServerSettingByKeyAsync(db, "board"); - const { hasFullAccess } = await getBoardPermissionsAsync(board); + const { hasFullAccess, hasChangeAccess } = await getBoardPermissionsAsync(board); const t = await getScopedI18n("board.setting"); + if (!hasChangeAccess) { + notFound(); + } + return ( diff --git a/apps/nextjs/src/app/[locale]/manage/settings/page.tsx b/apps/nextjs/src/app/[locale]/manage/settings/page.tsx index a651682e9..f877b4ded 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/page.tsx @@ -1,6 +1,8 @@ +import { notFound } from "next/navigation"; import { Stack, Title } from "@mantine/core"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { getScopedI18n } from "@homarr/translation/server"; import { CrawlingAndIndexingSettings } from "~/app/[locale]/manage/settings/_components/crawling-and-indexing.settings"; @@ -20,6 +22,12 @@ export async function generateMetadata() { } export default async function SettingsPage() { + const session = await auth(); + + if (!session?.user.permissions.includes("admin")) { + notFound(); + } + const serverSettings = await api.serverSettings.getAll(); const tSettings = await getScopedI18n("management.page.settings"); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx index d20f285a8..11c6d1dad 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx @@ -1,10 +1,12 @@ import Link from "next/link"; +import { notFound } from "next/navigation"; import { Alert, Anchor, Center, Group, Stack, Table, TableTbody, TableTd, TableTr, Text, Title } from "@mantine/core"; import { IconExclamationCircle } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; import { env } from "@homarr/auth/env.mjs"; +import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { everyoneGroup } from "@homarr/definitions"; import { getI18n, getScopedI18n } from "@homarr/translation/server"; @@ -24,6 +26,12 @@ interface GroupsDetailPageProps { } export default async function GroupsDetailPage({ params, searchParams }: GroupsDetailPageProps) { + const session = await auth(); + + if (!session?.user.permissions.includes("admin")) { + notFound(); + } + const t = await getI18n(); const tMembers = await getScopedI18n("management.page.group.setting.members"); const group = await api.group.getById({ id: params.id }); diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx index e3d8c6a5f..8ec60e2f3 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/page.tsx @@ -1,6 +1,8 @@ +import { notFound } from "next/navigation"; import { Card, Group, Stack, Text, Title } from "@mantine/core"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { everyoneGroup } from "@homarr/definitions"; import { getScopedI18n } from "@homarr/translation/server"; import { UserAvatar } from "@homarr/ui"; @@ -18,6 +20,12 @@ interface GroupsDetailPageProps { } export default async function GroupsDetailPage({ params }: GroupsDetailPageProps) { + const session = await auth(); + + if (!session?.user.permissions.includes("admin")) { + notFound(); + } + const group = await api.group.getById({ id: params.id }); const tGeneral = await getScopedI18n("management.page.group.setting.general"); const tGroupAction = await getScopedI18n("group.action"); diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx index 303d7b22d..f44397ab4 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/page.tsx @@ -1,7 +1,9 @@ import React from "react"; +import { notFound } from "next/navigation"; import { Card, CardSection, Divider, Group, Stack, Text, Title } from "@mantine/core"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { objectKeys } from "@homarr/common"; import type { GroupPermissionKey } from "@homarr/definitions"; import { groupPermissions } from "@homarr/definitions"; @@ -16,6 +18,12 @@ interface GroupPermissionsPageProps { } export default async function GroupPermissionsPage({ params }: GroupPermissionsPageProps) { + const session = await auth(); + + if (!session?.user.permissions.includes("admin")) { + notFound(); + } + const group = await api.group.getById({ id: params.id }); const tPermissions = await getScopedI18n("group.permission"); const t = await getI18n(); diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index 64863e2f2..26c38ba91 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -575,11 +575,14 @@ export const boardRouter = createTRPCRouter({ ); }); }), - importOldmarrConfig: protectedProcedure.input(importJsonFileSchema).mutation(async ({ input, ctx }) => { - const content = await input.file.text(); - const oldmarr = oldmarrConfigSchema.parse(JSON.parse(content)); - await importOldmarrAsync(ctx.db, oldmarr, input.configuration); - }), + importOldmarrConfig: permissionRequiredProcedure + .requiresPermission("board-create") + .input(importJsonFileSchema) + .mutation(async ({ input, ctx }) => { + const content = await input.file.text(); + const oldmarr = oldmarrConfigSchema.parse(JSON.parse(content)); + await importOldmarrAsync(ctx.db, oldmarr, input.configuration); + }), }); const noBoardWithSimilarNameAsync = async (db: Database, name: string, ignoredIds: string[] = []) => { diff --git a/packages/api/src/router/invite.ts b/packages/api/src/router/invite.ts index 8599555d3..c380f0003 100644 --- a/packages/api/src/router/invite.ts +++ b/packages/api/src/router/invite.ts @@ -6,11 +6,12 @@ import { invites } from "@homarr/db/schema/sqlite"; import { selectInviteSchema } from "@homarr/db/validationSchemas"; import { z } from "@homarr/validation"; -import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; import { throwIfCredentialsDisabled } from "./invite/checks"; export const inviteRouter = createTRPCRouter({ - getAll: protectedProcedure + getAll: permissionRequiredProcedure + .requiresPermission("admin") .output( z.array( selectInviteSchema @@ -40,7 +41,8 @@ export const inviteRouter = createTRPCRouter({ }, }); }), - createInvite: protectedProcedure + createInvite: permissionRequiredProcedure + .requiresPermission("admin") .input( z.object({ expirationDate: z.date(), @@ -65,7 +67,8 @@ export const inviteRouter = createTRPCRouter({ token, }; }), - deleteInvite: protectedProcedure + deleteInvite: permissionRequiredProcedure + .requiresPermission("admin") .input( z.object({ id: z.string(), diff --git a/packages/api/src/router/serverSettings.ts b/packages/api/src/router/serverSettings.ts index f1aa6ecdd..2fdb000f2 100644 --- a/packages/api/src/router/serverSettings.ts +++ b/packages/api/src/router/serverSettings.ts @@ -3,17 +3,18 @@ import type { ServerSettings } from "@homarr/server-settings"; import { defaultServerSettingsKeys } from "@homarr/server-settings"; import { validation, z } from "@homarr/validation"; -import { createTRPCRouter, onboardingProcedure, protectedProcedure, publicProcedure } from "../trpc"; +import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc"; import { nextOnboardingStepAsync } from "./onboard/onboard-queries"; export const serverSettingsRouter = createTRPCRouter({ getCulture: publicProcedure.query(async ({ ctx }) => { return await getServerSettingByKeyAsync(ctx.db, "culture"); }), - getAll: protectedProcedure.query(async ({ ctx }) => { + getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => { return await getServerSettingsAsync(ctx.db); }), - saveSettings: protectedProcedure + saveSettings: permissionRequiredProcedure + .requiresPermission("admin") .input( z.object({ settingsKey: z.enum(defaultServerSettingsKeys), diff --git a/packages/api/src/router/test/invite.spec.ts b/packages/api/src/router/test/invite.spec.ts index 6be30f7a9..927e7fd91 100644 --- a/packages/api/src/router/test/invite.spec.ts +++ b/packages/api/src/router/test/invite.spec.ts @@ -11,7 +11,7 @@ import { inviteRouter } from "../invite"; const defaultSession = { user: { id: createId(), - permissions: [], + permissions: ["admin"], colorScheme: "light", }, expires: new Date().toISOString(), diff --git a/packages/api/src/router/test/serverSettings.spec.ts b/packages/api/src/router/test/serverSettings.spec.ts index 4f08132be..ba2a7c784 100644 --- a/packages/api/src/router/test/serverSettings.spec.ts +++ b/packages/api/src/router/test/serverSettings.spec.ts @@ -15,7 +15,7 @@ vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session })); const defaultSession = { user: { id: createId(), - permissions: [], + permissions: ["admin"], colorScheme: "light", }, expires: new Date().toISOString(), diff --git a/packages/api/src/router/update-checker.ts b/packages/api/src/router/update-checker.ts index 730080873..4b36c3f93 100644 --- a/packages/api/src/router/update-checker.ts +++ b/packages/api/src/router/update-checker.ts @@ -1,9 +1,9 @@ import { updateCheckerRequestHandler } from "@homarr/request-handler/update-checker"; -import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; export const updateCheckerRouter = createTRPCRouter({ - getAvailableUpdates: protectedProcedure.query(async () => { + getAvailableUpdates: permissionRequiredProcedure.requiresPermission("admin").query(async () => { const handler = updateCheckerRequestHandler.handler({}); const data = await handler.getCachedOrUpdatedDataAsync({}); return data.data.availableUpdates; diff --git a/packages/api/src/router/widgets/dns-hole.ts b/packages/api/src/router/widgets/dns-hole.ts index a9b76be4f..3480ae874 100644 --- a/packages/api/src/router/widgets/dns-hole.ts +++ b/packages/api/src/router/widgets/dns-hole.ts @@ -10,7 +10,7 @@ import { controlsInputSchema } from "@homarr/integrations/types"; import { dnsHoleRequestHandler } from "@homarr/request-handler/dns-hole"; import { createManyIntegrationMiddleware, createOneIntegrationMiddleware } from "../../middlewares/integration"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; export const dnsHoleRouter = createTRPCRouter({ summary: publicProcedure @@ -62,7 +62,7 @@ export const dnsHoleRouter = createTRPCRouter({ }); }), - enable: publicProcedure + enable: protectedProcedure .unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole"))) .mutation(async ({ ctx: { integration } }) => { const client = integrationCreator(integration); @@ -75,7 +75,7 @@ export const dnsHoleRouter = createTRPCRouter({ }); }), - disable: publicProcedure + disable: protectedProcedure .input(controlsInputSchema) .unstable_concat(createOneIntegrationMiddleware("interact", ...getIntegrationKindsByCategory("dnsHole"))) .mutation(async ({ ctx: { integration }, input }) => { diff --git a/packages/api/src/router/widgets/indexer-manager.ts b/packages/api/src/router/widgets/indexer-manager.ts index 19dedb595..2bbf1356d 100644 --- a/packages/api/src/router/widgets/indexer-manager.ts +++ b/packages/api/src/router/widgets/indexer-manager.ts @@ -9,7 +9,7 @@ import { indexerManagerRequestHandler } from "@homarr/request-handler/indexer-ma import type { IntegrationAction } from "../../middlewares/integration"; import { createManyIntegrationMiddleware } from "../../middlewares/integration"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) => createManyIntegrationMiddleware(action, ...getIntegrationKindsByCategory("indexerManager")); @@ -54,7 +54,7 @@ export const indexerManagerRouter = createTRPCRouter({ }; }); }), - testAllIndexers: publicProcedure + testAllIndexers: protectedProcedure .unstable_concat(createIndexerManagerIntegrationMiddleware("interact")) .mutation(async ({ ctx }) => { await Promise.all( diff --git a/packages/api/src/router/widgets/notebook.ts b/packages/api/src/router/widgets/notebook.ts index 8fd369630..9c7d6da2b 100644 --- a/packages/api/src/router/widgets/notebook.ts +++ b/packages/api/src/router/widgets/notebook.ts @@ -5,10 +5,10 @@ import { eq } from "@homarr/db"; import { items } from "@homarr/db/schema/sqlite"; import { z } from "@homarr/validation"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; +import { createTRPCRouter, protectedProcedure } from "../../trpc"; export const notebookRouter = createTRPCRouter({ - updateContent: publicProcedure + updateContent: protectedProcedure .input( z.object({ itemId: z.string(), diff --git a/packages/api/src/router/widgets/smart-home.ts b/packages/api/src/router/widgets/smart-home.ts index 1b8b6ec64..d1a1fde85 100644 --- a/packages/api/src/router/widgets/smart-home.ts +++ b/packages/api/src/router/widgets/smart-home.ts @@ -7,7 +7,7 @@ import { z } from "@homarr/validation"; import type { IntegrationAction } from "../../middlewares/integration"; import { createOneIntegrationMiddleware } from "../../middlewares/integration"; -import { createTRPCRouter, publicProcedure } from "../../trpc"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc"; const createSmartHomeIntegrationMiddleware = (action: IntegrationAction) => createOneIntegrationMiddleware(action, ...getIntegrationKindsByCategory("smartHomeServer")); @@ -41,7 +41,7 @@ export const smartHomeRouter = createTRPCRouter({ }; }); }), - switchEntity: publicProcedure + switchEntity: protectedProcedure .unstable_concat(createSmartHomeIntegrationMiddleware("interact")) .input(z.object({ entityId: z.string() })) .mutation(async ({ ctx: { integration }, input }) => { @@ -53,7 +53,7 @@ export const smartHomeRouter = createTRPCRouter({ return success; }), - executeAutomation: publicProcedure + executeAutomation: protectedProcedure .unstable_concat(createSmartHomeIntegrationMiddleware("interact")) .input(z.object({ automationId: z.string() })) .mutation(async ({ ctx: { integration }, input }) => {