diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx
index c1127a6..7dc611e 100644
--- a/packages/app/src/app.tsx
+++ b/packages/app/src/app.tsx
@@ -4,6 +4,7 @@ import { auth, logout } from "@exosphere/client/auth";
import { useLocation, useRoute } from "@exosphere/client/router";
import { spherePath } from "@exosphere/client/router";
import { sphereState, sphereHandle, loadSphere } from "@exosphere/client/sphere";
+import { canAccessSettings } from "@exosphere/client/permissions";
import { isMultiSphere } from "@exosphere/client/config";
import { Link } from "@exosphere/client/link";
import * as ui from "@exosphere/client/ui.css";
@@ -18,6 +19,7 @@ import { SphereMembersPage } from "./pages/sphere-members.tsx";
import { SpherePermissionsPage } from "./pages/sphere-permissions.tsx";
import { SphereLabelsPage } from "./pages/sphere-labels.tsx";
import { Dashboard } from "./pages/dashboard.tsx";
+import { NotFoundPage } from "./pages/not-found.tsx";
import type { ModuleRoute } from "@exosphere/client/types";
import { feedsModule } from "@exosphere/feeds/client";
import { featureRequestsModule } from "@exosphere/feature-requests/client";
@@ -75,20 +77,6 @@ function MultiSphereDefaultPage() {
return ;
}
-function NotFoundPage() {
- return (
-
@@ -59,18 +46,34 @@ export function SettingsLayout({ active, children }: Props) {
{children}
diff --git a/packages/app/src/pages/sphere-labels.tsx b/packages/app/src/pages/sphere-labels.tsx
index 171a493..83067d3 100644
--- a/packages/app/src/pages/sphere-labels.tsx
+++ b/packages/app/src/pages/sphere-labels.tsx
@@ -8,6 +8,7 @@ import { GripVertical } from "lucide-preact";
import * as ui from "@exosphere/client/ui.css";
import * as cpUi from "./color-picker.css.ts";
import { SettingsLayout } from "./settings-layout.tsx";
+import { NotFoundPage } from "./not-found.tsx";
import {
getSphereLabels,
createSphereLabel,
@@ -33,7 +34,7 @@ export function SphereLabelsPage() {
const handle = sphereHandle.value;
if (!data || !handle) return null;
- if (!canDo("sphere", "manageLabels")) return null;
+ if (!canDo("sphere", "manageLabels")) return
;
return (
diff --git a/packages/app/src/pages/sphere-members.tsx b/packages/app/src/pages/sphere-members.tsx
index e2ceb7f..1024427 100644
--- a/packages/app/src/pages/sphere-members.tsx
+++ b/packages/app/src/pages/sphere-members.tsx
@@ -1,9 +1,10 @@
import { useSignal } from "@preact/signals";
import { sphereState, sphereHandle } from "@exosphere/client/sphere";
-import { canDo } from "@exosphere/client/permissions";
+import { canDo, canManageMembers } from "@exosphere/client/permissions";
import { useQuery } from "@exosphere/client/hooks";
import * as ui from "@exosphere/client/ui.css";
import { SettingsLayout } from "./settings-layout.tsx";
+import { NotFoundPage } from "./not-found.tsx";
import {
getSphereMembers,
inviteMember,
@@ -29,12 +30,7 @@ export function SphereMembersPage() {
const handle = sphereHandle.value;
if (!data || !handle) return null;
- // Allow access if user has any member management permission
- const canManageMembers =
- canDo("sphere", "inviteMember") ||
- canDo("sphere", "revokeMember") ||
- canDo("sphere", "updateMemberRole");
- if (!canManageMembers) return null;
+ if (!canManageMembers()) return ;
return (
diff --git a/packages/app/src/pages/sphere-permissions.tsx b/packages/app/src/pages/sphere-permissions.tsx
index b23a573..40e231b 100644
--- a/packages/app/src/pages/sphere-permissions.tsx
+++ b/packages/app/src/pages/sphere-permissions.tsx
@@ -4,6 +4,7 @@ import { canDo } from "@exosphere/client/permissions";
import { useQuery } from "@exosphere/client/hooks";
import * as ui from "@exosphere/client/ui.css";
import { SettingsLayout } from "./settings-layout.tsx";
+import { NotFoundPage } from "./not-found.tsx";
import { getSpherePermissions, updateSpherePermissions } from "../api/spheres.ts";
const roleLabels: Record = {
@@ -26,7 +27,7 @@ export function SpherePermissionsPage() {
const handle = sphereHandle.value;
if (!data || !handle) return null;
- if (!canDo("sphere", "updatePermissions")) return null;
+ if (!canDo("sphere", "updatePermissions")) return ;
return (
diff --git a/packages/app/src/pages/sphere-settings.css.ts b/packages/app/src/pages/sphere-settings.css.ts
index 3a04324..1fd5603 100644
--- a/packages/app/src/pages/sphere-settings.css.ts
+++ b/packages/app/src/pages/sphere-settings.css.ts
@@ -70,10 +70,6 @@ export const railLinkActive = style({
color: vars.color.primary,
});
-export const railLinkDanger = style({
- color: vars.color.danger,
-});
-
export const railGroup = style({
paddingBlock: "10px 4px",
paddingInline: "12px",
diff --git a/packages/app/src/pages/sphere-settings.tsx b/packages/app/src/pages/sphere-settings.tsx
index dcc8088..fee732a 100644
--- a/packages/app/src/pages/sphere-settings.tsx
+++ b/packages/app/src/pages/sphere-settings.tsx
@@ -1,11 +1,12 @@
import { useSignal } from "@preact/signals";
import { sphereState, sphereHandle, refreshSphere } from "@exosphere/client/sphere";
-import { canDo } from "@exosphere/client/permissions";
+import { canDo, canAccessSettings, canManageModules } from "@exosphere/client/permissions";
import { useLocation } from "@exosphere/client/router";
import { useQuery } from "@exosphere/client/hooks";
import * as ui from "@exosphere/client/ui.css";
import * as ss from "./sphere-settings.css.ts";
import { SettingsLayout } from "./settings-layout.tsx";
+import { NotFoundPage } from "./not-found.tsx";
import {
getSphereModules,
enableModule as apiEnableModule,
@@ -161,8 +162,10 @@ export function SphereSettingsPage() {
const handle = sphereHandle.value;
if (!data || !handle) return null;
+ if (!canAccessSettings()) return ;
const section = resolveSection(path ?? "");
+ if (section === "modules" && !canManageModules()) return ;
return (
diff --git a/packages/client/src/permissions.ts b/packages/client/src/permissions.ts
index d7b2218..ca3ffec 100644
--- a/packages/client/src/permissions.ts
+++ b/packages/client/src/permissions.ts
@@ -13,3 +13,28 @@ export function canDo(module: string, action: string): boolean {
export function useCanDo(module: string, action: string) {
return computed(() => canDo(module, action));
}
+
+/** True if the user can manage the sphere's enabled modules. */
+export function canManageModules(): boolean {
+ return canDo("sphere", "enableModule") || canDo("sphere", "disableModule");
+}
+
+/** True if the user can manage members (invite, revoke, update role). */
+export function canManageMembers(): boolean {
+ return (
+ canDo("sphere", "inviteMember") ||
+ canDo("sphere", "revokeMember") ||
+ canDo("sphere", "updateMemberRole")
+ );
+}
+
+/** True if the user has at least one settings-related permission.
+ * Used to gate the Settings entry point and the Settings page itself. */
+export function canAccessSettings(): boolean {
+ return (
+ canManageModules() ||
+ canManageMembers() ||
+ canDo("sphere", "manageLabels") ||
+ canDo("sphere", "updatePermissions")
+ );
+}