Compare commits

...
11 changed files with 8 additions and 31 deletions

No files matched your search

@@ -1,7 +1,5 @@
import { notFound } from "next/navigation";
import { Container, Fieldset, Group, Stack, Title } from "@mantine/core"; import { Container, Fieldset, Group, Stack, Title } from "@mantine/core";
import { auth } from "@homarr/auth/next";
import { api } from "@homarr/api/server"; import { api } from "@homarr/api/server";
import { getIntegrationName } from "@homarr/definitions"; import { getIntegrationName } from "@homarr/definitions";
import { getI18n, getScopedI18n } from "@homarr/translation/server"; import { getI18n, getScopedI18n } from "@homarr/translation/server";
@@ -18,11 +16,6 @@ interface EditIntegrationPageProps {
export default async function EditIntegrationPage(props: EditIntegrationPageProps) { export default async function EditIntegrationPage(props: EditIntegrationPageProps) {
const params = await props.params; const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("integration-full-all")) {
notFound();
}
const editT = await getScopedI18n("integration.page.edit"); const editT = await getScopedI18n("integration.page.edit");
const t = await getI18n(); const t = await getI18n();
const integration = await api.integration.byId({ id: params.id }).catch(catchTrpcNotFound); const integration = await api.integration.byId({ id: params.id }).catch(catchTrpcNotFound);
@@ -23,10 +23,7 @@ interface NewIntegrationPageProps {
export default async function IntegrationsNewPage(props: NewIntegrationPageProps) { export default async function IntegrationsNewPage(props: NewIntegrationPageProps) {
const searchParams = await props.searchParams; const searchParams = await props.searchParams;
const session = await auth(); const session = await auth();
if ( if (!session?.user.permissions.includes("integration-create")) {
!session?.user.permissions.includes("integration-full-all") ||
!session?.user.permissions.includes("integration-create")
) {
notFound(); notFound();
} }
@@ -1,5 +1,5 @@
import { Fragment } from "react"; import { Fragment } from "react";
import { notFound, redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { import {
AccordionControl, AccordionControl,
AccordionItem, AccordionItem,
@@ -50,9 +50,6 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) {
if (!session) { if (!session) {
redirect("/auth/login"); redirect("/auth/login");
} }
if (!session.user.permissions.includes("integration-full-all")) {
notFound();
}
const integrations = await api.integration.all(); const integrations = await api.integration.all();
const t = await getScopedI18n("integration"); const t = await getScopedI18n("integration");
@@ -71,7 +71,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
icon: IconAffiliateFilled, icon: IconAffiliateFilled,
href: "/manage/integrations", href: "/manage/integrations",
label: t("items.integrations"), label: t("items.integrations"),
hidden: !session?.user.permissions.includes("integration-full-all"), hidden: !session?.user.permissions.includes("integration-create"),
"data-onboarding-tour-id": "manage-integrations", "data-onboarding-tour-id": "manage-integrations",
}, },
{ {
+1 -1
View File
@@ -92,7 +92,7 @@ export const homeRouter = createTRPCRouter({
}); });
} }
if (ctx.session?.user.permissions.includes("integration-full-all")) { if (ctx.session?.user.permissions.includes("integration-create")) {
statistics.push({ statistics.push({
titleKey: "integration", titleKey: "integration",
subtitleKey: "resources", subtitleKey: "resources",
@@ -62,13 +62,12 @@ export const integrationRouter = createTRPCRouter({
requiredSecrets: def.secretKinds, requiredSecrets: def.secretKinds,
})); }));
}), }),
all: permissionRequiredProcedure all: protectedProcedure
.requiresPermission("integration-full-all")
.meta({ .meta({
mcp: { mcp: {
enabled: true, enabled: true,
description: description:
"List all configured integrations (connections to services like Sonarr, Radarr, Plex, etc.). Requires integration-full-all permission. Returns each integration's id, name, kind, url, and permissions. Use the 'id' field as 'integrationId' in other tools. Check permissions.hasUseAccess before reading data and permissions.hasInteractAccess before performing actions — false means the API key owner lacks that permission level for this integration, not an error", "List all configured integrations (connections to services like Sonarr, Radarr, Plex, etc.). Returns each integration's id, name, kind, url, and permissions. Use the 'id' field as 'integrationId' in other tools. Check permissions.hasUseAccess before reading data and permissions.hasInteractAccess before performing actions — false means the API key owner lacks that permission level for this integration, not an error",
}, },
}) })
.query(async ({ ctx }) => { .query(async ({ ctx }) => {
@@ -297,7 +296,6 @@ export const integrationRouter = createTRPCRouter({
}; };
}), }),
create: permissionRequiredProcedure create: permissionRequiredProcedure
.requiresPermission("integration-full-all")
.requiresPermission("integration-create") .requiresPermission("integration-create")
.meta({ .meta({
mcp: { mcp: {
@@ -104,7 +104,6 @@ export const releasesRouter = createTRPCRouter({
id: repositoryId, id: repositoryId,
provider: repository.provider, provider: repository.provider,
timestamp: response.timestamp, timestamp: response.timestamp,
success: true as const,
...response.data, ...response.data,
}; };
} catch (error) { } catch (error) {
@@ -119,7 +119,6 @@ export type HomarrDocumentationPath =
| "/docs/tags/variables" | "/docs/tags/variables"
| "/docs/tags/widgets" | "/docs/tags/widgets"
| "/docs/advanced/command-line" | "/docs/advanced/command-line"
| "/docs/advanced/command-line/development"
| "/docs/advanced/command-line/fix-usernames" | "/docs/advanced/command-line/fix-usernames"
| "/docs/advanced/command-line/integrations" | "/docs/advanced/command-line/integrations"
| "/docs/advanced/command-line/password-recovery" | "/docs/advanced/command-line/password-recovery"
@@ -95,9 +95,7 @@ export const globalCommandGroup = createGroup<Command>({
icon: IconPlug, icon: IconPlug,
name: tOption("newIntegration.label"), name: tOption("newIntegration.label"),
useInteraction: interaction.children(newIntegrationChildrenOptions), useInteraction: interaction.children(newIntegrationChildrenOptions),
hidden: hidden: !session?.user.permissions.includes("integration-create"),
!session?.user.permissions.includes("integration-full-all") ||
!session?.user.permissions.includes("integration-create"),
}, },
{ {
commandKey: "newUser", commandKey: "newUser",
@@ -82,7 +82,7 @@ export const pagesSearchGroup = createGroup<{
icon: IconPlug, icon: IconPlug,
path: "/manage/integrations", path: "/manage/integrations",
name: t("manageIntegration.label"), name: t("manageIntegration.label"),
hidden: !session?.user.permissions.includes("integration-full-all"), hidden: !session,
}, },
{ {
icon: IconSearch, icon: IconSearch,
-4
View File
@@ -5,10 +5,6 @@ export const appHrefSchema = z
.trim() .trim()
.url() .url()
.regex(/^(?!javascript)[a-zA-Z]*:\/\//i) // javascript: is not allowed, i for case insensitive (so Javascript: is also not allowed) .regex(/^(?!javascript)[a-zA-Z]*:\/\//i) // javascript: is not allowed, i for case insensitive (so Javascript: is also not allowed)
.or(z.string().trim().includes("[homarr_base]"))
.or(z.string().trim().includes("[homarr_hostname]"))
.or(z.string().trim().includes("[homarr_domain]"))
.or(z.string().trim().includes("[homarr_protocol]"))
.or(z.literal("")) .or(z.literal(""))
.transform((value) => (value.length === 0 ? null : value)) .transform((value) => (value.length === 0 ? null : value))
.nullable(); .nullable();