diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 79b1f7c..4387240 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -15,9 +15,16 @@ import cn from '~/utils/cn'; interface Props { configAvailable: boolean; - uiAccess: boolean; onboarding: boolean; user?: AuthSession['user']; + access: { + ui: boolean; + machines: boolean; + dns: boolean; + users: boolean; + policy: boolean; + settings: boolean; + }; } interface LinkProps { @@ -137,27 +144,45 @@ export default function Header(data: Props) { ) : undefined} - {data.uiAccess && !data.onboarding ? ( + {data.access.ui && !data.onboarding ? ( diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx index 14fc6f4..142ae14 100644 --- a/app/layouts/dashboard.tsx +++ b/app/layouts/dashboard.tsx @@ -1,6 +1,7 @@ import { XCircleFillIcon } from '@primer/octicons-react'; import { type LoaderFunctionArgs, redirect } from 'react-router'; import { Outlet, useLoaderData } from 'react-router'; +import { ErrorPopup } from '~/components/Error'; import type { LoadContext } from '~/server'; import { ResponseError } from '~/server/headscale/api-client'; import cn from '~/utils/cn'; @@ -65,3 +66,7 @@ export default function Layout() { ); } + +export function ErrorBoundary() { + return ; +} diff --git a/app/layouts/shell.tsx b/app/layouts/shell.tsx index 76f37cb..e9ac3fa 100644 --- a/app/layouts/shell.tsx +++ b/app/layouts/shell.tsx @@ -93,6 +93,20 @@ export async function loader({ debug: context.config.debug, user: session.get('user'), uiAccess: check, + access: { + ui: await context.sessions.check(request, Capabilities.ui_access), + dns: await context.sessions.check(request, Capabilities.read_network), + users: await context.sessions.check(request, Capabilities.read_users), + policy: await context.sessions.check(request, Capabilities.read_policy), + machines: await context.sessions.check( + request, + Capabilities.read_machines, + ), + settings: await context.sessions.check( + request, + Capabilities.read_feature, + ), + }, onboarding: request.url.endsWith('/onboarding'), }; } catch { diff --git a/app/routes/dns/dns-actions.ts b/app/routes/dns/dns-actions.ts index 3655e09..73604f8 100644 --- a/app/routes/dns/dns-actions.ts +++ b/app/routes/dns/dns-actions.ts @@ -1,10 +1,20 @@ import { ActionFunctionArgs, data } from 'react-router'; import { LoadContext } from '~/server'; +import { Capabilities } from '~/server/web/roles'; export async function dnsAction({ request, context, }: ActionFunctionArgs) { + const check = await context.sessions.check( + request, + Capabilities.write_network, + ); + + if (!check) { + return data({ success: false }, 403); + } + if (!context.hs.writable()) { return data({ success: false }, 403); } diff --git a/app/routes/dns/overview.tsx b/app/routes/dns/overview.tsx index ef382bb..01834c9 100644 --- a/app/routes/dns/overview.tsx +++ b/app/routes/dns/overview.tsx @@ -3,6 +3,7 @@ import { useLoaderData } from 'react-router'; import Code from '~/components/Code'; import Notice from '~/components/Notice'; import type { LoadContext } from '~/server'; +import { Capabilities } from '~/server/web/roles'; import ManageDomains from './components/manage-domains'; import ManageNS from './components/manage-ns'; import ManageRecords from './components/manage-records'; @@ -11,11 +12,30 @@ import ToggleMagic from './components/toggle-magic'; import { dnsAction } from './dns-actions'; // We do not want to expose every config value -export async function loader({ context }: LoaderFunctionArgs) { +export async function loader({ + request, + context, +}: LoaderFunctionArgs) { if (!context.hs.readable()) { throw new Error('No configuration is available'); } + const check = await context.sessions.check( + request, + Capabilities.read_network, + ); + if (!check) { + // Not authorized to view this page + throw new Error( + 'You do not have permission to view this page. Please contact your administrator.', + ); + } + + const writablePermission = await context.sessions.check( + request, + Capabilities.write_network, + ); + const config = context.hs.c!; const dns = { prefixes: config.prefixes, @@ -29,6 +49,7 @@ export async function loader({ context }: LoaderFunctionArgs) { return { ...dns, + access: writablePermission, writable: context.hs.writable(), }; } @@ -46,7 +67,7 @@ export default function Page() { } allNs.global = data.nameservers; - const isDisabled = data.writable === false; + const isDisabled = data.access === false || data.writable === false; return (
@@ -56,6 +77,12 @@ export default function Page() { the configuration )} + {data.access ? undefined : ( + + Your permissions do not allow you to modify the DNS settings for this + tailnet. + + )}