From 2cfa806176f991d47a09c630125bf6b287e5d8dd Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 16 Aug 2026 16:23:01 +0200 Subject: [PATCH] feat: recalculate permission scope and add permission matrix UI - Gate app list procedures (getPaginated/all/search) behind app-modify-all - Filter integration list procedures by per-integration access - Bundle create permissions with their modify/full parents on group save - Align manage nav, home stat cards and page gates with the new scope - Add permission matrix UI with presets and effective-permission preview - Show scoped empty states and delegated-admin notices on the integrations page --- .screenshots/groups-list.png | Bin 0 -> 29724 bytes .screenshots/manage-home.png | Bin 0 -> 74628 bytes .screenshots/matrix-initial.png | Bin 0 -> 40700 bytes apps/docs/docs/management/apps/index.mdx | 2 +- .../docs/management/integrations/index.mdx | 2 + apps/docs/docs/management/users/index.mdx | 58 +++- .../src/app/[locale]/manage/apps/page.tsx | 38 ++- .../app/[locale]/manage/integrations/page.tsx | 66 +++-- .../nextjs/src/app/[locale]/manage/layout.tsx | 11 +- .../src/app/[locale]/manage/not-found.tsx | 6 +- apps/nextjs/src/app/[locale]/manage/page.tsx | 9 +- .../permissions/_group-permission-form.tsx | 250 +++++++++++++++++- .../users/groups/[id]/permissions/page.tsx | 105 +++----- packages/api/src/router/app.ts | 9 +- packages/api/src/router/group.ts | 62 ++++- packages/api/src/router/home.ts | 41 ++- .../router/integration/integration-router.ts | 198 +++++++------- packages/api/src/router/test/app.spec.ts | 42 ++- packages/api/src/router/test/group.spec.ts | 83 ++++++ .../integration/integration-router.spec.ts | 135 ++++++++++ packages/definitions/src/permissions.ts | 41 +++ .../definitions/src/test/permissions.spec.ts | 74 +++++- packages/translation/src/lang/en.json | 25 +- 23 files changed, 982 insertions(+), 275 deletions(-) create mode 100644 .screenshots/groups-list.png create mode 100644 .screenshots/manage-home.png create mode 100644 .screenshots/matrix-initial.png diff --git a/.screenshots/groups-list.png b/.screenshots/groups-list.png new file mode 100644 index 0000000000000000000000000000000000000000..3c43f074773abd73c1c99583a407daad137ba429 GIT binary patch literal 29724 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 z z{T9d!XgO%xY1-Lf+xvQ9YSWMIpwD|I->X((Q8}~l-0KTob63mlE8Iu-Z<(D`&nI;L zN~eLpNTe?HNW;(a+11O@O@fS}0dq(Cb9*+1*V=j9hTPX(>{<2}+!|aZ-zMnR-tjV_ zY={rfy6Nz~-E57{&}ZMa_`Hsrq)TVM^2o#$pigm9A|l)0>G6JP-wTOTQl}<=aU4PGXe7 zLIqH$i>S!3E_NLUf#`$bPJ;!8;^8l_68`bt6%XIrj0@jyKEm%~Jz_to#m3^<9L-j; z>Et`;Hcue#dl#_8YwUF60umRLST|>Tyoj&!eMVPGyNilW7JUqo@NS|i=Ph@h`es}{ z&7ZVTvKdMA9%(v1`u!Tmz&$%XdlEjDTn4@nB~l>-c6pDIa;^SwkKQTY+4gTqI|Wt6 zyWdL}_l;WzeH6T(isbZ6`P{p1=r!DAx49_oyRKGkzHzJi;78H{k=fY##`&bOrOEnU z-5&F3Td$p4dHwD)lm=(3>pRbbrcxTZE zvoc>S#7!9Y-Eg43zJ*9|I;gQU+)FCn-OiU>OWdIGS*u;0&E6QUAhytL7<#%CQK0+L zlbJ_pUz3~dM>DMo9|z{-h>o9ivEkvjeivFejX*RU4efd*kweVb`dnbUAmtLIV? z(gt%QQfI!xc>s9*iTp&iDwA#Dq|*7Y{?R4m-VB@Jc3ZSK?{NFUSh)jP`1b?Y$ZX*1hKz#_4?1H1Z_2@-|$Jma$0R znU(0>W@DWsapzGtAN#N*p}J@uxBW%u1lW^5(s|ld+ODt^yHzg)e%bnn&WzC~pI=Ck zY1IdMDA_7u?-&{Az1DJFpc4`_urEw3e^Ny6w!2h$U?qWt@J(S-QWx3_*WJxk?sJ}=IY*9UGT&UyL+j)?9T4;r)Fxcr_s^|XZx6M_*!-|lBR&(4KI=*l!?nYcA%&!CWF!f$ z@RP*W@bIwB!w!};2y3?P#<4N}!@0iJywu!o?`!GRnwYHsdmZM8!fS6!qPTcNq8BF2 ziF;?ml6(93{Q@29kT9N!M`;pHc2*&w&CU*9m%~XPecMCqpWav;8ptI~NmgbSZwPNJ zdh+)0Nm?FfzU$)VMwe&x6`nc~eje}l?FPIXKc=VE@%<Rht_*t%|Zeq zr{(mddZ7DD^i*|U{I$sYFxv13dz$5|qV;uwd4+lfaWS#K-Agd#eC?e9p{P2OC1@`b z40AA@tbMw##_4=2JF1KIa?A^IrF8qvjq^I7H_4Qa=liLX1~(`EXH?ZsD^1RG3mywH zSEIS{6WG8DELUUKwtL^JA!sGU$K^O@p06xCd@>20rW$lBs{F0+ea|kSbVjYv=o^uW z8>|v-L!E}APe0u$`p}cD{2`76#<5)|>Dqwvp7HLa~@UCP}MCj(lF zx*ZRO&4t3~UDA9PZR~H09&vHWSN+?UwUDnVCRb+k2hnk6c<1F%IraQJOWoJaPS2=6 zxs4y4O~fjt#A5GUCWHzQnAl18W1Yws^~=nBA?9`6Z~o?bU(aP@MjS3jT)f^@y7Gmj zLg$T{d)C6Lq#48alQ9{w7UCP~0AuT`_HY~&Hsh`Dbd6jVVNXvcG ziRsVn{!}YDWsV6fTSY}tn%UQVuVf3%hFYX+va#8Rgmec6>X=z~Lsxw*K23wUs}Ah?TPpW2Jk9iGA9=%c zhWt})@qP43EdpPU@>H?2$36m{>LyI$cr-6-bWEK375W%QI=*>LKYtV_!s zzG{t*H&djJlaB}Z#QFL-^J~NJOJBUM&V8-4S`0(T!ONr0xX}-VSL|QVoD+{4VeBF+ z<8+_bQHG`SH0oNUeAq}lkmtUXSuXYT1Nj|^vS{ZPiP|0gIn#9dmVexC`W{0Uxh4AG zj54AZzv?z#aEClC>1L$Q)qeh!AlULI`YZ2u$Bvgb$!mp!=gIqa;#}_1vd@h<+1}UW zV^ejyJ4RB!JTDJSyqs>Dj(t(~w{UMF;Zp|(FWq)z;UCAy1LYBlNj7(Rtiq%vyh8C% MUiDs{?4#%Z8_N`D_5c6? literal 0 HcmV?d00001 diff --git a/apps/docs/docs/management/apps/index.mdx b/apps/docs/docs/management/apps/index.mdx index a2fde8e48..49d6e504c 100644 --- a/apps/docs/docs/management/apps/index.mdx +++ b/apps/docs/docs/management/apps/index.mdx @@ -53,4 +53,4 @@ Note that the ping URL only accepts `http://` and `https://` URLs — custom URI On the apps management page you find all apps that exist in your Homarr instance. Search by name or URL, open an app directly, or use its row actions to edit and delete it. A filtered search can be cleared without leaving the page. -Any signed-in user can view this list. Creating, editing, and deleting apps requires the corresponding app permission; board permissions are not required. +Viewing this list requires the `app-modify-all` permission. Creating an app requires `app-create`, editing requires `app-modify-all`, and deleting requires `app-full-all`; board permissions are not required. diff --git a/apps/docs/docs/management/integrations/index.mdx b/apps/docs/docs/management/integrations/index.mdx index a7ccbe1a6..1abcf6814 100644 --- a/apps/docs/docs/management/integrations/index.mdx +++ b/apps/docs/docs/management/integrations/index.mdx @@ -17,6 +17,8 @@ To create an integration, choose **New integration** and select the supported ap The integrations management page uses a searchable list. Each row shows the integration type and URL, with the actions you have permission to use. +This page is only visible to users with the global `integration-full-all` permission or with full access on at least one integration. Without the global permission, only the integrations you have full access to are shown. + Next, you'll need to fill out the required fields to configure the integration: ![](./img/form-create-integration.png) diff --git a/apps/docs/docs/management/users/index.mdx b/apps/docs/docs/management/users/index.mdx index dd14f4a6d..4908cb231 100644 --- a/apps/docs/docs/management/users/index.mdx +++ b/apps/docs/docs/management/users/index.mdx @@ -163,19 +163,59 @@ Members can be removed from the group using the remove button at the end: #### Permissions tab In this tab, you manage what permissions the members of this group will have. -Currently, the following permissions are available: +Permissions are organized by category (boards, apps, integrations, and so on). Each category is a row with a +segmented control that lets you pick a single access level. Selecting a level grants that level and every lower +level in the same category. Choosing **None** removes all permissions for that category. -- Boards - - **Create boards**: Allow members to create boards +Selecting **Administrator** grants full access to everything. While the administrator level is selected, every +other category is disabled and shows a **Granted via Administrator** caption, because administrators already have +all permissions. Deselecting it (choosing **None** on the administrator row) re-enables the other categories. + +##### Presets + +Above the rows you can apply a preset to fill in a common configuration in one click: + +- **Viewer**: view access to boards, use access to apps and integrations, and viewing media. +- **Editor**: modify access to boards and apps, interact with integrations, modify search engines (includes creating), and viewing media. +- **Admin**: grants the administrator permission (full access to everything). + +When the current selection does not match any preset, a **Custom** badge is shown. + +##### Implied permissions + +Some levels include additional permissions automatically. When a level implies extra permissions (for example, +full board access also allows creating boards), a dimmed `also grants: …` hint is shown under the category row. + +##### Effective permissions + +Below the rows, the **Effective permissions** panel can be expanded to review everything members of this group +will be able to do, including implied permissions, grouped by category with a count for each. + +Each category below is a segmented control that starts with **None**; the remaining segments are the access levels +in ascending order (higher levels include the lower ones and may imply extra permissions shown in the hint): + +- Boards — None - **View all boards**: Allow members to view all boards - **Modify all boards**: Allow members to modify all boards (Does not include access control and danger zone) - - **Full board access**: Allow members to view, modify, and delete all boards (Including access control and danger zone) -- Integrations - - **Create integrations**: Allow members to create integrations - - **Use all integrations**: Allows members to add any integrations to their boards + - **Full board access**: Allow members to view, modify, and delete all boards (Including access control and danger zone; includes creating boards) +- Apps — None + - **Use all apps**: Allow members to add any apps to their boards + - **Modify all apps**: Allow members to modify all apps (includes creating apps) + - **Full app access**: Allow members to manage, use and delete any app +- Integrations — None + - **Use all integrations**: Allow members to add any integrations to their boards - **Interact with any integration**: Allow members to interact with any integration - - **Full integration access**: Allow members to manage, use and interact with any integration -- **Administrator**: Members with this permission have full access to all features and settings + - **Full integration access**: Allow members to manage, use and interact with any integration (includes creating integrations) +- Search engines — None + - **Modify all search engines**: Allow members to modify all search engines (includes creating search engines) + - **Full search engine access**: Allow members to manage and delete any search engine +- Medias — None + - **View all medias**: Allow members to view all medias + - **Full media access**: Allow members to manage and delete any media (includes uploading medias) +- Other — None + - **View logs**: Allow members to view logs +- Administrator — None + - **Administrator**: Members with this permission have full access to all features and settings Custom Widget authoring, import, migration, preview, secrets, Workshop access, and placement configuration are reserved for administrators and are not separate assignable group permissions. Board modifiers can still move, resize, change diff --git a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx index f37c730cd..536bd6ca8 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx @@ -1,5 +1,5 @@ import { Fragment } from "react"; -import { redirect } from "next/navigation"; +import { notFound, redirect } from "next/navigation"; import { ActionIcon, ActionIconGroup, Anchor, Avatar, Text } from "@mantine/core"; import { IconBox, IconPencil } from "@tabler/icons-react"; import { z } from "zod/v4"; @@ -41,12 +41,12 @@ interface AppsPageProps { export default async function AppsPage(props: AppsPageProps) { const session = await auth(); if (!session) redirect("/auth/login"); + if (!session.user.permissions.includes("app-modify-all")) notFound(); const searchParams = searchParamsSchema.parse(await props.searchParams); const { items: apps, totalCount } = await api.app.getPaginated(searchParams); const t = await getScopedI18n("app"); const canCreate = session.user.permissions.includes("app-create"); - const canModify = session.user.permissions.includes("app-modify-all"); const canDelete = session.user.permissions.includes("app-full-all"); const hasSearch = Boolean(searchParams.search?.trim()); @@ -100,7 +100,6 @@ export default async function AppsPage(props: AppsPageProps) { @@ -113,12 +112,11 @@ export default async function AppsPage(props: AppsPageProps) { interface AppItemProps { app: RouterOutputs["app"]["getPaginated"]["items"][number]; - canModify: boolean; canDelete: boolean; editLabel: string; } -const AppItem = ({ app, canModify, canDelete, editLabel }: AppItemProps) => { +const AppItem = ({ app, canDelete, editLabel }: AppItemProps) => { const descriptionLines = app.description?.split("\n"); const safeHref = getSafeAppHref(app.href); @@ -157,23 +155,19 @@ const AppItem = ({ app, canModify, canDelete, editLabel }: AppItemProps) => { ) : undefined } actions={ - canModify || canDelete ? ( - - {canModify && ( - - - - )} - {canDelete && } - - ) : undefined + + + + + {canDelete && } + } /> ); diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx index 785137023..6488b5f26 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; -import { ActionIcon, ActionIconGroup, Anchor, Badge, Text } from "@mantine/core"; +import { ActionIcon, ActionIconGroup, Anchor, Badge, Stack, Text } from "@mantine/core"; import { IconPencil, IconPlugX } from "@tabler/icons-react"; import { z } from "zod/v4"; @@ -32,8 +32,11 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) { const t = await getScopedI18n("integration"); const canCreate = session.user.permissions.includes("integration-create"); const hasGlobalFullAccess = session.user.permissions.includes("integration-full-all"); + const canSee = hasGlobalFullAccess || integrations.some((integration) => integration.permissions.hasFullAccess); + const query = searchParams.search?.trim().toLocaleLowerCase() ?? ""; const filteredIntegrations = integrations + .filter((integration) => hasGlobalFullAccess || integration.permissions.hasFullAccess) .filter((integration) => { if (!query) return true; return [integration.name, getIntegrationName(integration.kind), integration.url].some((value) => @@ -53,7 +56,13 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) { ariaLabel={t("page.list.ariaLabel")} itemCount={filteredIntegrations.length} emptyState={ - hasSearch ? ( + !canSee ? ( + + ) : hasSearch ? ( {filteredIntegrations.map((integration) => ( - + ))} ); - return filteredIntegrations.length > 0 ? {page} : page; + const pageContent = + filteredIntegrations.length > 0 ? {page} : page; + + if (hasGlobalFullAccess) { + return pageContent; + } + + return ( + + + {t("page.list.scopedNote")} + + {pageContent} + + ); } interface IntegrationItemProps { integration: RouterOutputs["integration"]["all"][number]; - canManage: boolean; } -const IntegrationItem = async ({ integration, canManage }: IntegrationItemProps) => { +const IntegrationItem = async ({ integration }: IntegrationItemProps) => { const t = await getScopedI18n("integration"); const kindName = getIntegrationName(integration.kind); const safeUrl = getSafeApplicationUrl(integration.url); @@ -143,21 +161,19 @@ const IntegrationItem = async ({ integration, canManage }: IntegrationItemProps) ) : undefined } actions={ - canManage ? ( - - - - - - - ) : undefined + + + + + + } /> ); diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx index 7fa486360..a011d67cd 100644 --- a/apps/nextjs/src/app/[locale]/manage/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx @@ -28,6 +28,7 @@ import { IconUsersGroup, } from "@tabler/icons-react"; +import { api } from "@homarr/api/server"; import { getRscUserSettingsAsync } from "@homarr/api/user-server"; import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; @@ -65,6 +66,12 @@ export default async function ManageLayout({ children }: PropsWithChildren) { sessionPromise, shouldRunManageTourPromise, ]); + const hasGlobalIntegrationFullAccess = session?.user.permissions.includes("integration-full-all") ?? false; + const hasDelegatedIntegrationFullAccess = + !hasGlobalIntegrationFullAccess && session + ? (await api.integration.all()).some((integration) => integration.permissions.hasFullAccess) + : false; + const canManageIntegrations = hasGlobalIntegrationFullAccess || hasDelegatedIntegrationFullAccess; const navigationLinks: NavigationLink[] = [ { label: t("items.home"), @@ -82,7 +89,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { icon: IconBox, href: "/manage/apps", label: t("items.apps"), - hidden: !session?.user.permissions.includes("app-create"), + hidden: !session?.user.permissions.includes("app-modify-all"), iconProps: { strokeWidth: 2.5, }, @@ -92,7 +99,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { icon: IconAffiliateFilled, href: "/manage/integrations", label: t("items.integrations"), - hidden: !session?.user.permissions.includes("integration-create"), + hidden: !canManageIntegrations, "data-onboarding-tour-id": "manage-integrations", }, { diff --git a/apps/nextjs/src/app/[locale]/manage/not-found.tsx b/apps/nextjs/src/app/[locale]/manage/not-found.tsx index f6931af9f..9b13fc3d7 100644 --- a/apps/nextjs/src/app/[locale]/manage/not-found.tsx +++ b/apps/nextjs/src/app/[locale]/manage/not-found.tsx @@ -1,6 +1,7 @@ -import { Center, Stack, Text, Title } from "@mantine/core"; +import { Anchor, Center, Stack, Text, Title } from "@mantine/core"; import { getScopedI18n } from "@homarr/translation/server"; +import { Link } from "@homarr/ui"; export default async function NotFound() { const t = await getScopedI18n("management.notFound"); @@ -9,6 +10,9 @@ export default async function NotFound() { {t("title")} {t("text")} + + {t("backToHome")} + ); diff --git a/apps/nextjs/src/app/[locale]/manage/page.tsx b/apps/nextjs/src/app/[locale]/manage/page.tsx index c7a3a1813..cb78663be 100644 --- a/apps/nextjs/src/app/[locale]/manage/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/page.tsx @@ -29,7 +29,12 @@ export default async function ManagementPage() { {statistics.map((statistic) => ( - + @@ -42,7 +47,7 @@ export default async function ManagementPage() { {t(`statistic.${statistic.titleKey}`)} - + ))} diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/_group-permission-form.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/_group-permission-form.tsx index 1ae1b119c..e91ec1d02 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/_group-permission-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/permissions/_group-permission-form.tsx @@ -2,16 +2,37 @@ import type { PropsWithChildren } from "react"; import { useCallback } from "react"; -import { Button, Card, Group, Switch, Text, Transition } from "@mantine/core"; +import { + Badge, + Button, + Card, + Collapse, + Group, + SegmentedControl, + Stack, + Text, + Transition, + UnstyledButton, +} from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import { IconShieldExclamation } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; -import { objectEntries } from "@homarr/common"; -import type { GroupPermissionKey } from "@homarr/definitions"; -import { groupPermissionKeys } from "@homarr/definitions"; +import { objectEntries, objectKeys } from "@homarr/common"; +import type { GroupPermissionKey, PermissionMatrixCategory, PermissionMatrixState } from "@homarr/definitions"; +import { + getPermissionsWithChildren, + groupPermissionKeys, + matrixStateToPermissions, + permissionMatrix, + permissionsToMatrixState, +} from "@homarr/definitions"; import { createFormContext } from "@homarr/form"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n, useScopedI18n } from "@homarr/translation/client"; +export type PermissionLabels = Partial>; + const [FormProvider, useFormContext, useForm] = createFormContext(); interface PermissionFormProps { @@ -39,7 +60,7 @@ export const PermissionForm = ({ children, initialPermissions }: PropsWithChildr }); return ( -

+ event.preventDefault()}> {children} ); @@ -47,15 +68,222 @@ export const PermissionForm = ({ children, initialPermissions }: PropsWithChildr type FormType = Record; -export const PermissionSwitch = ({ name }: { name: GroupPermissionKey }) => { - const form = useFormContext(); +type MatrixForm = ReturnType; - const props = form.getInputProps(name, { - withError: false, - type: "checkbox", +const emptyMatrixState = (): PermissionMatrixState => + objectKeys(permissionMatrix).reduce((acc, category) => { + acc[category] = { level: 0 }; + return acc; + }, {} as PermissionMatrixState); + +export const getMatrixLevel = (values: FormType, category: PermissionMatrixCategory): number => { + const checkedKeys = groupPermissionKeys.filter((key) => values[key]); + return permissionsToMatrixState(checkedKeys)[category].level; +}; + +export const applyMatrixLevel = (form: MatrixForm, category: PermissionMatrixCategory, level: number) => { + const targetKeys = new Set(matrixStateToPermissions({ ...emptyMatrixState(), [category]: { level } })); + permissionMatrix[category].levels.forEach((key) => { + form.setFieldValue(key, targetKeys.has(key)); }); +}; - return ; +const presets = { + viewer: { board: 1, app: 1, integration: 1, media: 1 }, + editor: { board: 2, app: 2, integration: 2, "search-engine": 1, media: 1 }, + admin: { admin: 1 }, +} satisfies Record>>; + +export type PermissionPreset = keyof typeof presets; + +const presetToFormState = (preset: PermissionPreset): FormType => { + const levels: Partial> = presets[preset]; + const state = objectKeys(permissionMatrix).reduce((acc, category) => { + acc[category] = { level: levels[category] ?? 0 }; + return acc; + }, {} as PermissionMatrixState); + const enabledKeys = new Set(matrixStateToPermissions(state)); + + return groupPermissionKeys.reduce((acc, key) => { + acc[key] = enabledKeys.has(key); + return acc; + }, {} as FormType); +}; + +export const applyPreset = (form: MatrixForm, preset: PermissionPreset) => { + form.setValues(presetToFormState(preset)); +}; + +export const getActivePreset = (values: FormType): PermissionPreset | null => { + return ( + objectKeys(presets).find((preset) => { + const presetValues = presetToFormState(preset); + return groupPermissionKeys.every((key) => presetValues[key] === values[key]); + }) ?? null + ); +}; + +interface MatrixImpliedHintProps { + category: PermissionMatrixCategory; + permissionLabels: PermissionLabels; +} + +export const MatrixImpliedHint = ({ category, permissionLabels }: MatrixImpliedHintProps) => { + const form = useFormContext(); + const tPermissions = useScopedI18n("group.permission"); + + const level = getMatrixLevel(form.getValues(), category); + if (level === 0) { + return null; + } + + const checkedKeys: GroupPermissionKey[] = permissionMatrix[category].levels.slice(0, level); + const implied = getPermissionsWithChildren(checkedKeys).filter((key) => !checkedKeys.includes(key)); + if (implied.length === 0) { + return null; + } + + const labels = implied.map((key) => permissionLabels[key] ?? key); + + return ( + + {tPermissions("matrix.impliedHint", { permissions: labels.join(", ") })} + + ); +}; + +interface MatrixRowProps { + category: PermissionMatrixCategory; + permissionLabels: PermissionLabels; +} + +export const MatrixRow = ({ category, permissionLabels }: MatrixRowProps) => { + const form = useFormContext(); + const tPermissions = useScopedI18n("group.permission"); + + const values = form.getValues(); + const level = getMatrixLevel(values, category); + const isDanger = category === "admin"; + const disabledByAdmin = !isDanger && getMatrixLevel(values, "admin") > 0; + + const data = [ + { value: "0", label: tPermissions("matrix.none") }, + ...permissionMatrix[category].levels.map((key, index) => ({ + value: String(index + 1), + label: permissionLabels[key] ?? key, + })), + ]; + + return ( + + + + {isDanger && } + + {tPermissions(`${category}.title`)} + + + applyMatrixLevel(form, category, Number(value))} + /> + {disabledByAdmin && ( + + {tPermissions("matrix.grantedViaAdmin")} + + )} + + + + ); +}; + +export const PresetButtons = () => { + const form = useFormContext(); + const tPermissions = useScopedI18n("group.permission"); + const activePreset = getActivePreset(form.getValues()); + + return ( + + + {tPermissions("matrix.preset.label")} + + {(["viewer", "editor", "admin"] as const).map((preset) => ( + + ))} + {activePreset === null && {tPermissions("matrix.preset.custom")}} + + ); +}; + +interface EffectivePermissionPreviewProps { + permissionLabels: PermissionLabels; +} + +export const EffectivePermissionPreview = ({ permissionLabels }: EffectivePermissionPreviewProps) => { + const form = useFormContext(); + const tPermissions = useScopedI18n("group.permission"); + const [opened, { toggle }] = useDisclosure(false); + + const values = form.getValues(); + const checkedKeys = groupPermissionKeys.filter((key) => values[key]); + const effective = getPermissionsWithChildren(checkedKeys); + + const grouped = objectKeys(permissionMatrix) + .map((category) => ({ + category, + keys: effective.filter((key) => key === category || key.startsWith(`${category}-`)), + })) + .filter(({ keys }) => keys.length > 0); + + return ( + + + + + {tPermissions("matrix.effective.title")} + + {tPermissions("matrix.effective.description")} + + + {effective.length} + + + + + {grouped.length === 0 ? ( + + {tPermissions("matrix.effective.empty")} + + ) : ( + grouped.map(({ category, keys }) => ( + + + {tPermissions(`${category}.title`)} + + + {keys.length} + + + {keys.map((key) => permissionLabels[key] ?? key).join(", ")} + + + )) + )} + + + + ); }; interface SaveAffixProps { 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 0ba8ae9d1..39e1b10d4 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,15 +1,21 @@ -import React from "react"; import { notFound } from "next/navigation"; -import { Card, CardSection, Divider, Group, Stack, Text, Title } from "@mantine/core"; +import { Stack, 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"; +import { groupPermissions, permissionMatrix } from "@homarr/definitions"; import { getI18n, getScopedI18n } from "@homarr/translation/server"; -import { PermissionForm, PermissionSwitch, SaveAffix } from "./_group-permission-form"; +import type { PermissionLabels } from "./_group-permission-form"; +import { + EffectivePermissionPreview, + MatrixRow, + PermissionForm, + PresetButtons, + SaveAffix, +} from "./_group-permission-form"; interface GroupPermissionsPageProps { params: Promise<{ @@ -26,27 +32,25 @@ export default async function GroupPermissionsPage(props: GroupPermissionsPagePr } const group = await api.group.getById({ id: params.id }); - const tPermissions = await getScopedI18n("group.permission"); const t = await getI18n(); + const permissionLabels = await buildPermissionLabels(); + return ( {t("management.page.group.setting.permissions.title")} - {objectKeys(groupPermissions).map((group) => { - const isDanger = group === "admin"; + - return ( - - - {tPermissions(`${group}.title`)} - - - - ); - })} + + {objectKeys(permissionMatrix).map((category) => ( + + ))} + + + @@ -55,66 +59,19 @@ export default async function GroupPermissionsPage(props: GroupPermissionsPagePr ); } -interface PermissionCardProps { - group: keyof typeof groupPermissions; - isDanger: boolean; -} +const buildPermissionLabels = async (): Promise => { + const permissionLabels: PermissionLabels = {}; -const PermissionCard = async ({ group, isDanger }: PermissionCardProps) => { - const t = await getScopedI18n(`group.permission.${group}.item`); - const item = groupPermissions[group]; - const permissions = typeof item !== "boolean" ? item : ([group] as "admin"[]); + for (const category of objectKeys(groupPermissions)) { + const tItem = await getScopedI18n(`group.permission.${category}.item`); + const item = groupPermissions[category]; + const suffixes = typeof item !== "boolean" ? item : (["admin"] as const); - return ( - - - {permissions.map((permission, index) => ( - - - - {index < permissions.length - 1 && ( - - - - )} - - ))} - - - ); -}; - -const createGroupPermissionKey = (group: keyof typeof groupPermissions, permission: string): GroupPermissionKey => { - if (typeof groupPermissions[group] === "boolean") { - return group as GroupPermissionKey; + suffixes.forEach((suffix) => { + const key = (typeof item !== "boolean" ? `${category}-${suffix}` : "admin") as GroupPermissionKey; + permissionLabels[key] = tItem(`${suffix}.label`); + }); } - return `${group}-${permission}` as GroupPermissionKey; -}; - -interface PermissionRowProps { - name: GroupPermissionKey; - label: string; - description: string; -} - -const PermissionRow = ({ name, label, description }: PermissionRowProps) => { - return ( - - - {label} - {description} - - - - ); + return permissionLabels; }; diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts index 3698d7f86..d0e4b4b18 100644 --- a/packages/api/src/router/app.ts +++ b/packages/api/src/router/app.ts @@ -18,7 +18,8 @@ import { AppAccessControl } from "./app/app-access-control"; const defaultIcon = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/homarr.svg"; export const appRouter = createTRPCRouter({ - getPaginated: protectedProcedure + getPaginated: permissionRequiredProcedure + .requiresPermission("app-modify-all") .meta({ openapi: { method: "GET", @@ -50,7 +51,8 @@ export const appRouter = createTRPCRouter({ totalCount, }; }), - all: protectedProcedure + all: permissionRequiredProcedure + .requiresPermission("app-modify-all") .input(z.void()) .output(z.array(selectAppSchema)) .meta({ @@ -67,7 +69,8 @@ export const appRouter = createTRPCRouter({ orderBy: asc(apps.name), }); }), - search: protectedProcedure + search: permissionRequiredProcedure + .requiresPermission("app-modify-all") .input( z.object({ query: z.string(), diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts index 5e8f8834b..5396af94d 100644 --- a/packages/api/src/router/group.ts +++ b/packages/api/src/router/group.ts @@ -9,6 +9,7 @@ import type { Database } from "@homarr/db"; import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db"; import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { groupMembers, groupPermissions, groups, onboarding, users } from "@homarr/db/schema"; +import type { GroupPermissionKey } from "@homarr/definitions"; import { everyoneGroup } from "@homarr/definitions"; import { byIdSchema, paginatedSchema } from "@homarr/validation/common"; import { @@ -22,6 +23,13 @@ import { import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, protectedProcedure } from "../trpc"; +const createPermissionParents = { + "app-create": "app-modify-all", + "integration-create": "integration-full-all", + "board-create": "board-modify-all", + "search-engine-create": "search-engine-modify-all", +} satisfies Partial>; + export const groupRouter = createTRPCRouter({ getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => { const dbGroups = await ctx.db.query.groups.findMany({ @@ -302,16 +310,52 @@ export const groupRouter = createTRPCRouter({ .mutation(async ({ input, ctx }) => { await throwIfGroupNotFoundAsync(ctx.db, input.groupId); - await ctx.db.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId)); + const permissions = [ + ...new Set( + input.permissions.flatMap((permission) => { + const parent = createPermissionParents[permission as keyof typeof createPermissionParents] as + | GroupPermissionKey + | undefined; + return parent ? [permission, parent] : [permission]; + }), + ), + ]; - if (input.permissions.length > 0) { - await ctx.db.insert(groupPermissions).values( - input.permissions.map((permission) => ({ - groupId: input.groupId, - permission, - })), - ); - } + await handleTransactionsAsync(ctx.db, { + async handleAsync(db, schema) { + await db.transaction(async (transaction) => { + await transaction + .delete(schema.groupPermissions) + .where(eq(schema.groupPermissions.groupId, input.groupId)); + if (permissions.length === 0) { + return; + } + await transaction.insert(schema.groupPermissions).values( + permissions.map((permission) => ({ + groupId: input.groupId, + permission, + })), + ); + }); + }, + handleSync(db) { + db.transaction((transaction) => { + transaction.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId)).run(); + if (permissions.length === 0) { + return; + } + transaction + .insert(groupPermissions) + .values( + permissions.map((permission) => ({ + groupId: input.groupId, + permission, + })), + ) + .run(); + }); + }, + }); }), transferOwnership: permissionRequiredProcedure .requiresPermission("admin") diff --git a/packages/api/src/router/home.ts b/packages/api/src/router/home.ts index f33cea48f..c7ed9c138 100644 --- a/packages/api/src/router/home.ts +++ b/packages/api/src/router/home.ts @@ -1,4 +1,6 @@ import { isProviderEnabled } from "@homarr/auth/server"; +import { constructIntegrationPermissions } from "@homarr/auth/shared"; +import type { Database } from "@homarr/db"; import { db, eq, inArray, or } from "@homarr/db"; import { apps, @@ -6,6 +8,8 @@ import { boardUserPermissions, groupMembers, groups, + integrationGroupPermissions, + integrationUserPermissions, integrations, invites, medias, @@ -16,6 +20,33 @@ import type { TranslationObject } from "@homarr/translation"; import { createTRPCRouter, publicProcedure } from "../trpc"; +const getFullAccessIntegrationIdsAsync = async (db: Database, userId: string) => { + const groupsOfCurrentUser = await db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, userId), + }); + + const accessibleIntegrations = await db.query.integrations.findMany({ + columns: { + id: true, + }, + with: { + userPermissions: { + where: eq(integrationUserPermissions.userId, userId), + }, + groupPermissions: { + where: inArray( + integrationGroupPermissions.groupId, + groupsOfCurrentUser.map((group) => group.groupId), + ), + }, + }, + }); + + return accessibleIntegrations + .filter((integration) => constructIntegrationPermissions(integration, null).hasFullAccess) + .map((integration) => integration.id); +}; + interface HomeStatistic { titleKey: keyof TranslationObject["management"]["page"]["home"]["statistic"]; subtitleKey: keyof TranslationObject["management"]["page"]["home"]["statisticLabel"]; @@ -92,16 +123,20 @@ export const homeRouter = createTRPCRouter({ }); } - if (ctx.session?.user.permissions.includes("integration-create")) { + const hasGlobalFullIntegrationAccess = ctx.session?.user.permissions.includes("integration-full-all") ?? false; + const fullAccessIntegrationIds = hasGlobalFullIntegrationAccess + ? [] + : await getFullAccessIntegrationIdsAsync(ctx.db, ctx.session?.user.id ?? ""); + if (hasGlobalFullIntegrationAccess || fullAccessIntegrationIds.length > 0) { statistics.push({ titleKey: "integration", subtitleKey: "resources", - count: await db.$count(integrations), + count: hasGlobalFullIntegrationAccess ? await db.$count(integrations) : fullAccessIntegrationIds.length, path: "/manage/integrations", }); } - if (ctx.session?.user.permissions.includes("app-create")) { + if (ctx.session?.user.permissions.includes("app-modify-all")) { statistics.push({ titleKey: "app", subtitleKey: "resources", diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 552cd853f..d1235f9c8 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -1,12 +1,13 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod/v4"; +import type { Session } from "@homarr/auth"; import { hasQueryAccessToIntegrationsAsync } from "@homarr/auth/server"; import { constructIntegrationPermissions } from "@homarr/auth/shared"; import { createId, objectEntries } from "@homarr/common"; import { decryptSecret, encryptSecret } from "@homarr/common/server"; import { createLogger } from "@homarr/core/infrastructure/logs"; -import type { Database } from "@homarr/db"; +import type { Database, SQL } from "@homarr/db"; import { and, asc, eq, handleTransactionsAsync, inArray, like, or } from "@homarr/db"; import { apps, @@ -17,7 +18,7 @@ import { integrationSecrets, integrationUserPermissions, } from "@homarr/db/schema"; -import type { IntegrationSecretKind } from "@homarr/definitions"; +import type { GroupPermissionKey, IntegrationPermission, IntegrationSecretKind } from "@homarr/definitions"; import { getIntegrationKindsByCategory, getPermissionsWithParents, @@ -71,80 +72,18 @@ export const integrationRouter = createTRPCRouter({ }, }) .query(async ({ ctx }) => { - const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ - where: eq(groupMembers.userId, ctx.session.user.id), - }); - - const integrations = await ctx.db.query.integrations.findMany({ - with: { - userPermissions: { - where: eq(integrationUserPermissions.userId, ctx.session.user.id), - }, - groupPermissions: { - where: inArray( - integrationGroupPermissions.groupId, - groupsOfCurrentUser.map((group) => group.groupId), - ), - }, - }, - }); - return integrations - .map((integration) => { - const permissions = constructIntegrationPermissions(integration, ctx.session); - - return { - id: integration.id, - name: integration.name, - kind: integration.kind, - url: integration.url, - permissions, - }; - }) - .toSorted( - (integrationA, integrationB) => - integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind), - ); + return await getAccessibleIntegrationsAsync(ctx); }), allThatSupportSearch: protectedProcedure.query(async ({ ctx }) => { - const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ - where: eq(groupMembers.userId, ctx.session.user.id), - }); - - const integrationsFromDb = await ctx.db.query.integrations.findMany({ - with: { - userPermissions: { - where: eq(integrationUserPermissions.userId, ctx.session.user.id), - }, - groupPermissions: { - where: inArray( - integrationGroupPermissions.groupId, - groupsOfCurrentUser.map((group) => group.groupId), - ), - }, - }, - where: inArray( + return await getAccessibleIntegrationsAsync( + ctx, + inArray( integrations.kind, objectEntries(integrationDefs) .filter(([_, integration]) => [...integration.category].includes("search")) .map(([kind, _]) => kind), ), - }); - return integrationsFromDb - .map((integration) => { - const permissions = constructIntegrationPermissions(integration, ctx.session); - - return { - id: integration.id, - name: integration.name, - kind: integration.kind, - url: integration.url, - permissions, - }; - }) - .toSorted( - (integrationA, integrationB) => - integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind), - ); + ); }), allOfGivenCategory: protectedProcedure .input( @@ -153,42 +92,10 @@ export const integrationRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ - where: eq(groupMembers.userId, ctx.session.user.id), - }); - - const intergrationKinds = getIntegrationKindsByCategory(input.category); - - const integrationsFromDb = await ctx.db.query.integrations.findMany({ - with: { - userPermissions: { - where: eq(integrationUserPermissions.userId, ctx.session.user.id), - }, - groupPermissions: { - where: inArray( - integrationGroupPermissions.groupId, - groupsOfCurrentUser.map((group) => group.groupId), - ), - }, - }, - where: inArray(integrations.kind, intergrationKinds), - }); - return integrationsFromDb - .map((integration) => { - const permissions = constructIntegrationPermissions(integration, ctx.session); - - return { - id: integration.id, - name: integration.name, - kind: integration.kind, - url: integration.url, - permissions, - }; - }) - .toSorted( - (integrationA, integrationB) => - integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind), - ); + return await getAccessibleIntegrationsAsync( + ctx, + inArray(integrations.kind, getIntegrationKindsByCategory(input.category)), + ); }), search: protectedProcedure .meta({ @@ -205,11 +112,32 @@ export const integrationRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - return await ctx.db.query.integrations.findMany({ + const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, ctx.session.user.id), + }); + + const integrationsFromDb = await ctx.db.query.integrations.findMany({ where: like(integrations.name, `%${input.query}%`), orderBy: asc(integrations.name), - limit: input.limit, + with: { + userPermissions: { + where: eq(integrationUserPermissions.userId, ctx.session.user.id), + }, + groupPermissions: { + where: inArray( + integrationGroupPermissions.groupId, + groupsOfCurrentUser.map((group) => group.groupId), + ), + }, + }, }); + + return integrationsFromDb + .filter((integration) => hasIntegrationQueryAccess(ctx.session, integration)) + .slice(0, input.limit) + .map( + ({ userPermissions: _userPermissions, groupPermissions: _groupPermissions, ...integration }) => integration, + ); }), // This is used to get the integrations by their ids it's public because it's needed to get integrations data in the boards byIds: publicProcedure.input(z.array(z.string())).query(async ({ ctx, input }) => { @@ -774,6 +702,62 @@ export const integrationRouter = createTRPCRouter({ }), }); +const globalIntegrationPermissions = [ + "integration-use-all", + "integration-interact-all", + "integration-full-all", +] satisfies GroupPermissionKey[]; + +const hasGlobalIntegrationAccess = (session: Session | null) => + globalIntegrationPermissions.some((permission) => session?.user.permissions.includes(permission) ?? false); + +const hasIntegrationQueryAccess = ( + session: Session | null, + integration: { + userPermissions: { permission: IntegrationPermission }[]; + groupPermissions: { permission: IntegrationPermission }[]; + }, +) => hasGlobalIntegrationAccess(session) || constructIntegrationPermissions(integration, session).hasUseAccess; + +const getAccessibleIntegrationsAsync = async (ctx: { db: Database; session: Session }, where?: SQL) => { + const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, ctx.session.user.id), + }); + + const integrationsFromDb = await ctx.db.query.integrations.findMany({ + where, + with: { + userPermissions: { + where: eq(integrationUserPermissions.userId, ctx.session.user.id), + }, + groupPermissions: { + where: inArray( + integrationGroupPermissions.groupId, + groupsOfCurrentUser.map((group) => group.groupId), + ), + }, + }, + }); + + return integrationsFromDb + .filter((integration) => hasIntegrationQueryAccess(ctx.session, integration)) + .map((integration) => { + const permissions = constructIntegrationPermissions(integration, ctx.session); + + return { + id: integration.id, + name: integration.name, + kind: integration.kind, + url: integration.url, + permissions, + }; + }) + .toSorted( + (integrationA, integrationB) => + integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind), + ); +}; + interface IntegrationRouterContext { db: Database; session: Parameters[2]; diff --git a/packages/api/src/router/test/app.spec.ts b/packages/api/src/router/test/app.spec.ts index 3a30bacc9..6706ca6a4 100644 --- a/packages/api/src/router/test/app.spec.ts +++ b/packages/api/src/router/test/app.spec.ts @@ -33,7 +33,7 @@ describe("all should return all apps", () => { const caller = appRouter.createCaller({ db, deviceType: undefined, - session: createDefaultSession(), + session: createDefaultSession(["app-modify-all"]), }); await db.insert(apps).values([ @@ -74,6 +74,20 @@ describe("all should return all apps", () => { // Assert await expect(actAsync()).rejects.toThrow("UNAUTHORIZED"); }); + test("should throw FORBIDDEN without app-modify-all permission", async () => { + // Arrange + const caller = appRouter.createCaller({ + db: createDb(), + deviceType: undefined, + session: createDefaultSession(), + }); + + // Act + const actAsync = async () => await caller.all(); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("getPaginated should return apps to authenticated users", () => { @@ -87,12 +101,12 @@ describe("getPaginated should return apps to authenticated users", () => { await expect(caller.getPaginated({ page: 1, pageSize: 10 })).rejects.toThrow("UNAUTHORIZED"); }); - test("should return apps without board permissions", async () => { + test("should return apps with app-modify-all permission", async () => { const db = createDb(); const caller = appRouter.createCaller({ db, deviceType: undefined, - session: createDefaultSession(), + session: createDefaultSession(["app-modify-all"]), }); await db.insert(apps).values({ id: "1", name: "Homarr", iconUrl: "https://homarr.dev/icon.svg" }); @@ -101,6 +115,28 @@ describe("getPaginated should return apps to authenticated users", () => { items: [{ id: "1" }], }); }); + + test("should throw FORBIDDEN without app-modify-all permission", async () => { + const caller = appRouter.createCaller({ + db: createDb(), + deviceType: undefined, + session: createDefaultSession(), + }); + + await expect(caller.getPaginated({ page: 1, pageSize: 10 })).rejects.toThrow("Permission denied"); + }); +}); + +describe("search should return apps to authenticated users", () => { + test("should throw FORBIDDEN without app-modify-all permission", async () => { + const caller = appRouter.createCaller({ + db: createDb(), + deviceType: undefined, + session: createDefaultSession(), + }); + + await expect(caller.search({ query: "Homarr" })).rejects.toThrow("Permission denied"); + }); }); describe("byId should return an app by id", () => { diff --git a/packages/api/src/router/test/group.spec.ts b/packages/api/src/router/test/group.spec.ts index 5deeb3e2f..8af77faa9 100644 --- a/packages/api/src/router/test/group.spec.ts +++ b/packages/api/src/router/test/group.spec.ts @@ -449,6 +449,89 @@ describe("savePermissions should save permissions for group", () => { expect(permissions.map(({ permission }) => permission)).toEqual(["integration-use-all", "board-full-all"]); }); + test("with a create permission it should also persist its parent permission", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); + + const groupId = createId(); + await db.insert(groups).values({ + id: groupId, + name: "Group", + position: 1, + }); + + // Act + await caller.savePermissions({ + groupId, + permissions: ["app-create"], + }); + + // Assert + const permissions = await db.query.groupPermissions.findMany({ + where: eq(groupPermissions.groupId, groupId), + }); + + expect(permissions.map(({ permission }) => permission).toSorted()).toEqual(["app-create", "app-modify-all"]); + }); + + test("with a create permission and its parent it should persist exactly two rows", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); + + const groupId = createId(); + await db.insert(groups).values({ + id: groupId, + name: "Group", + position: 1, + }); + + // Act + await caller.savePermissions({ + groupId, + permissions: ["app-create", "app-modify-all"], + }); + + // Assert + const permissions = await db.query.groupPermissions.findMany({ + where: eq(groupPermissions.groupId, groupId), + }); + + expect(permissions.length).toBe(2); + expect(permissions.map(({ permission }) => permission).toSorted()).toEqual(["app-create", "app-modify-all"]); + }); + + test("with an empty permission list it should clear all existing permissions", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, deviceType: undefined, session: adminSession }); + + const groupId = createId(); + await db.insert(groups).values({ + id: groupId, + name: "Group", + position: 1, + }); + await db.insert(groupPermissions).values([ + { groupId, permission: "admin" }, + { groupId, permission: "board-full-all" }, + ]); + + // Act + await caller.savePermissions({ + groupId, + permissions: [], + }); + + // Assert + const permissions = await db.query.groupPermissions.findMany({ + where: eq(groupPermissions.groupId, groupId), + }); + + expect(permissions.length).toBe(0); + }); + test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); diff --git a/packages/api/src/router/test/integration/integration-router.spec.ts b/packages/api/src/router/test/integration/integration-router.spec.ts index 1591debb2..fde85f951 100644 --- a/packages/api/src/router/test/integration/integration-router.spec.ts +++ b/packages/api/src/router/test/integration/integration-router.spec.ts @@ -58,6 +58,141 @@ describe("all should return all integrations", () => { expect(result[1]!.kind).toBe("homeAssistant"); expect(result.every(({ permissions }) => permissions.hasFullAccess && permissions.hasUseAccess)).toBe(true); }); + + test("with integration use access should return all integrations", async () => { + const db = createDb(); + const caller = integrationRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSessionWithPermissions(["integration-use-all"]), + }); + + await db.insert(integrations).values([ + { + id: "1", + name: "Home assistant", + kind: "homeAssistant", + url: "http://homeassist.local", + }, + { + id: "2", + name: "Home plex server", + kind: "plex", + url: "http://plex.local", + }, + ]); + + const result = await caller.all(); + expect(result.length).toBe(2); + }); + + test("without global integration access should only return integrations with use access", async () => { + const db = createDb(); + const caller = integrationRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSessionWithPermissions(), + }); + + await db.insert(integrations).values([ + { + id: "1", + name: "Home assistant", + kind: "homeAssistant", + url: "http://homeassist.local", + }, + { + id: "2", + name: "Home plex server", + kind: "plex", + url: "http://plex.local", + }, + ]); + await db.insert(users).values({ id: defaultUserId }); + await db.insert(integrationUserPermissions).values({ + integrationId: "1", + userId: defaultUserId, + permission: "use", + }); + + const result = await caller.all(); + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe("1"); + expect(result[0]!.permissions.hasUseAccess).toBe(true); + }); + + test("with a per-integration interact grant should return the integration with use access", async () => { + const db = createDb(); + const caller = integrationRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSessionWithPermissions(), + }); + + await db.insert(integrations).values([ + { + id: "1", + name: "Home assistant", + kind: "homeAssistant", + url: "http://homeassist.local", + }, + { + id: "2", + name: "Home plex server", + kind: "plex", + url: "http://plex.local", + }, + ]); + await db.insert(users).values({ id: defaultUserId }); + await db.insert(integrationUserPermissions).values({ + integrationId: "1", + userId: defaultUserId, + permission: "interact", + }); + + const result = await caller.all(); + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe("1"); + expect(result[0]!.permissions.hasUseAccess).toBe(true); + }); +}); + +describe("search should return accessible integrations", () => { + test("without global integration access should only return integrations with use access as plain rows", async () => { + const db = createDb(); + const caller = integrationRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSessionWithPermissions(), + }); + + await db.insert(integrations).values([ + { + id: "1", + name: "Home assistant", + kind: "homeAssistant", + url: "http://homeassist.local", + }, + { + id: "2", + name: "Home plex server", + kind: "plex", + url: "http://plex.local", + }, + ]); + await db.insert(users).values({ id: defaultUserId }); + await db.insert(integrationUserPermissions).values({ + integrationId: "1", + userId: defaultUserId, + permission: "use", + }); + + const result = await caller.search({ query: "Home" }); + expect(result).toHaveLength(1); + expect(result[0]!.id).toBe("1"); + expect(result[0]).not.toHaveProperty("userPermissions"); + expect(result[0]).not.toHaveProperty("groupPermissions"); + }); }); describe("mediaRequestSearchTargets should return accessible media search integrations", () => { diff --git a/packages/definitions/src/permissions.ts b/packages/definitions/src/permissions.ts index 0ceb648be..3b43d59a5 100644 --- a/packages/definitions/src/permissions.ts +++ b/packages/definitions/src/permissions.ts @@ -117,3 +117,44 @@ export const groupPermissionKeys = objectKeys(groupPermissions).reduce((acc, key } return acc; }, [] as GroupPermissionKey[]); + +/** + * Ordered access levels per category used by the group permission matrix UI. + * Each level entry is a full {@link GroupPermissionKey}; higher levels imply the lower ones + * through {@link groupPermissionParents}. + */ +export const permissionMatrix = { + board: { levels: ["board-view-all", "board-modify-all", "board-full-all"] }, + app: { levels: ["app-use-all", "app-modify-all", "app-full-all"] }, + integration: { levels: ["integration-use-all", "integration-interact-all", "integration-full-all"] }, + "search-engine": { levels: ["search-engine-modify-all", "search-engine-full-all"] }, + media: { levels: ["media-view-all", "media-full-all"] }, + other: { levels: ["other-view-logs"] }, + admin: { levels: ["admin"] }, +} as const satisfies Record; + +export type PermissionMatrixCategory = keyof typeof permissionMatrix; + +export type PermissionMatrixState = Record; + +export const permissionsToMatrixState = (permissions: GroupPermissionKey[]): PermissionMatrixState => { + const permissionSet = new Set(permissions); + + return objectKeys(permissionMatrix).reduce((acc, category) => { + let level = 0; + permissionMatrix[category].levels.forEach((key, index) => { + if (permissionSet.has(key)) { + level = index + 1; + } + }); + acc[category] = { level }; + return acc; + }, {} as PermissionMatrixState); +}; + +export const matrixStateToPermissions = (state: PermissionMatrixState): GroupPermissionKey[] => { + return objectKeys(permissionMatrix).reduce((acc, category) => { + acc.push(...permissionMatrix[category].levels.slice(0, state[category].level)); + return acc; + }, [] as GroupPermissionKey[]); +}; diff --git a/packages/definitions/src/test/permissions.spec.ts b/packages/definitions/src/test/permissions.spec.ts index 48a4ad665..e74a938f0 100644 --- a/packages/definitions/src/test/permissions.spec.ts +++ b/packages/definitions/src/test/permissions.spec.ts @@ -1,7 +1,12 @@ import { describe, expect, test } from "vitest"; import type { GroupPermissionKey } from "../permissions"; -import { getPermissionsWithChildren, getPermissionsWithParents } from "../permissions"; +import { + getPermissionsWithChildren, + getPermissionsWithParents, + matrixStateToPermissions, + permissionsToMatrixState, +} from "../permissions"; describe("getPermissionsWithParents should return the correct permissions", () => { test.each([ @@ -45,3 +50,70 @@ describe("getPermissionsWithChildren should return the correct permissions", () expect(getPermissionsWithChildren(input)).toEqual(expect.arrayContaining(expectedOutput)); }); }); + +describe("permissionsToMatrixState should resolve the highest present level per category", () => { + test("resolves the highest present level key", () => { + const state = permissionsToMatrixState(["board-view-all", "board-modify-all"]); + expect(state.board.level).toBe(2); + }); + + test("resolves the admin category", () => { + expect(permissionsToMatrixState(["admin"]).admin.level).toBe(1); + }); + + test("resolves an empty selection to all zero", () => { + const state = permissionsToMatrixState([]); + expect(Object.values(state).every(({ level }) => level === 0)).toBe(true); + }); +}); + +describe("matrixStateToPermissions should expand levels into permission keys", () => { + test("expands a level into the ordered level keys", () => { + const permissions = matrixStateToPermissions({ + board: { level: 2 }, + app: { level: 0 }, + integration: { level: 0 }, + "search-engine": { level: 0 }, + media: { level: 0 }, + other: { level: 0 }, + admin: { level: 0 }, + }); + expect(permissions).toEqual(["board-view-all", "board-modify-all"]); + }); + + test("expands the admin category into the admin key", () => { + const permissions = matrixStateToPermissions({ + board: { level: 0 }, + app: { level: 0 }, + integration: { level: 0 }, + "search-engine": { level: 0 }, + media: { level: 0 }, + other: { level: 0 }, + admin: { level: 1 }, + }); + expect(permissions).toEqual(["admin"]); + }); +}); + +describe("matrix helpers should round-trip stably", () => { + test.each([ + [["board-view-all", "board-modify-all"]], + [["integration-use-all", "integration-interact-all", "integration-full-all"]], + [["admin"]], + [[]], + [ + [ + "app-use-all", + "app-modify-all", + "search-engine-modify-all", + "search-engine-full-all", + "media-view-all", + "other-view-logs", + ], + ], + ] satisfies [GroupPermissionKey[]][])("expect %s to round-trip", (permissions) => { + const roundTripped = matrixStateToPermissions(permissionsToMatrixState(permissions)); + expect(roundTripped).toEqual(expect.arrayContaining(permissions)); + expect(roundTripped).toHaveLength(permissions.length); + }); +}); diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 91e4178c5..464dfb114 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -653,6 +653,23 @@ "description": "Allow members to manage and delete any search engine" } } + }, + "matrix": { + "none": "None", + "impliedHint": "also grants: {permissions}", + "grantedViaAdmin": "Granted via Administrator", + "preset": { + "label": "Presets", + "viewer": "Viewer", + "editor": "Editor", + "admin": "Admin", + "custom": "Custom" + }, + "effective": { + "title": "Effective permissions", + "description": "Everything members of this group will be able to do, including implied permissions", + "empty": "No permissions selected" + } } }, "memberNotice": { @@ -1581,6 +1598,7 @@ "title": "Integrations", "ariaLabel": "Configured integrations", "search": "Search integrations", + "scopedNote": "Showing only integrations you have full access to", "action": { "edit": "Edit {name}", "delete": "Delete {name}" @@ -1591,7 +1609,9 @@ "action": "Create your first integration", "filteredTitle": "No integrations match your search", "filteredDescription": "No integrations match \"{search}\". Try another name, type, or URL.", - "clearSearch": "Clear search" + "clearSearch": "Clear search", + "noPermissionTitle": "No manageable integrations", + "noPermissionDescription": "You don't have full management access to any integrations. Ask your administrator." } }, "create": { @@ -6134,7 +6154,8 @@ }, "notFound": { "title": "Not Found", - "text": "Could not find requested resource" + "text": "Could not find requested resource", + "backToHome": "Back to management home" }, "error": { "title": "This page couldn't load",