diff --git a/server/lib/config.ts b/server/lib/config.ts
index 2f5b5e99..a071a955 100644
--- a/server/lib/config.ts
+++ b/server/lib/config.ts
@@ -95,6 +95,10 @@ export class Config {
? "true"
: "false";
+ process.env.FLAGS_ENABLE_CLIENTS = parsedConfig.flags?.disable_clients
+ ? "true"
+ : "false";
+
this.rawConfig = parsedConfig;
}
diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts
index 5bce0db1..f07e5628 100644
--- a/server/lib/readConfigFile.ts
+++ b/server/lib/readConfigFile.ts
@@ -225,7 +225,8 @@ export const configSchema = z
enable_redis: z.boolean().optional(),
disable_local_sites: z.boolean().optional(),
disable_basic_wireguard_sites: z.boolean().optional(),
- disable_config_managed_domains: z.boolean().optional()
+ disable_config_managed_domains: z.boolean().optional(),
+ enable_clients: z.boolean().optional()
})
.optional()
})
diff --git a/server/middlewares/verifyClientsEnabled.ts b/server/middlewares/verifyClientsEnabled.ts
new file mode 100644
index 00000000..2b573dba
--- /dev/null
+++ b/server/middlewares/verifyClientsEnabled.ts
@@ -0,0 +1,29 @@
+import { Request, Response, NextFunction } from "express";
+import createHttpError from "http-errors";
+import HttpCode from "@server/types/HttpCode";
+import config from "@server/lib/config";
+
+export async function verifyClientsEnabled(
+ req: Request,
+ res: Response,
+ next: NextFunction
+) {
+ try {
+ if (!config.getRawConfig().flags?.enable_redis) {
+ return next(
+ createHttpError(
+ HttpCode.NOT_IMPLEMENTED,
+ "Clients are not enabled on this server."
+ )
+ );
+ }
+ return next();
+ } catch (error) {
+ return next(
+ createHttpError(
+ HttpCode.INTERNAL_SERVER_ERROR,
+ "Failed to check if clients are enabled"
+ )
+ );
+ }
+}
diff --git a/server/routers/external.ts b/server/routers/external.ts
index b0980fa5..946dfd88 100644
--- a/server/routers/external.ts
+++ b/server/routers/external.ts
@@ -41,6 +41,7 @@ import { createNewt, getNewtToken } from "./newt";
import { getOlmToken } from "./olm";
import rateLimit from "express-rate-limit";
import createHttpError from "http-errors";
+import { verifyClientsEnabled } from "@server/middlewares/verifyClientsEnabled";
// Root routes
export const unauthenticated = Router();
@@ -116,6 +117,7 @@ authenticated.get(
authenticated.get(
"/org/:orgId/pick-client-defaults",
+ verifyClientsEnabled,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createClient),
client.pickClientDefaults
@@ -123,6 +125,7 @@ authenticated.get(
authenticated.get(
"/org/:orgId/clients",
+ verifyClientsEnabled,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listClients),
client.listClients
@@ -130,6 +133,7 @@ authenticated.get(
authenticated.get(
"/org/:orgId/client/:clientId",
+ verifyClientsEnabled,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getClient),
client.getClient
@@ -137,6 +141,7 @@ authenticated.get(
authenticated.put(
"/org/:orgId/client",
+ verifyClientsEnabled,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createClient),
client.createClient
@@ -144,6 +149,7 @@ authenticated.put(
authenticated.delete(
"/client/:clientId",
+ verifyClientsEnabled,
verifyClientAccess,
verifyUserHasAction(ActionsEnum.deleteClient),
client.deleteClient
@@ -151,6 +157,7 @@ authenticated.delete(
authenticated.post(
"/client/:clientId",
+ verifyClientsEnabled,
verifyClientAccess, // this will check if the user has access to the client
verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client
client.updateClient
diff --git a/src/app/[orgId]/settings/clients/layout.tsx b/src/app/[orgId]/settings/clients/layout.tsx
new file mode 100644
index 00000000..59a46414
--- /dev/null
+++ b/src/app/[orgId]/settings/clients/layout.tsx
@@ -0,0 +1,21 @@
+import { redirect } from "next/navigation";
+import { pullEnv } from "@app/lib/pullEnv";
+
+export const dynamic = "force-dynamic";
+
+interface SettingsLayoutProps {
+ children: React.ReactNode;
+ params: Promise<{ orgId: string }>;
+}
+
+export default async function SettingsLayout(props: SettingsLayoutProps) {
+ const params = await props.params;
+ const { children } = props;
+ const env = pullEnv();
+
+ if (!env.flags.enableClients) {
+ redirect(`/${params.orgId}/settings`);
+ }
+
+ return children;
+}
diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx
index a05a2718..3d63c9ed 100644
--- a/src/app/[orgId]/settings/layout.tsx
+++ b/src/app/[orgId]/settings/layout.tsx
@@ -19,7 +19,8 @@ import UserProvider from "@app/providers/UserProvider";
import { Layout } from "@app/components/Layout";
import { SidebarNavItem, SidebarNavProps } from "@app/components/SidebarNav";
import { orgNavItems } from "@app/app/navigation";
-import { getTranslations } from 'next-intl/server';
+import { getTranslations } from "next-intl/server";
+import { pullEnv } from "@app/lib/pullEnv";
export const dynamic = "force-dynamic";
@@ -28,39 +29,6 @@ export const metadata: Metadata = {
description: ""
};
-const topNavItems = [
- {
- title: "Sites",
- href: "/{orgId}/settings/sites",
- icon:
- },
- {
- title: "Resources",
- href: "/{orgId}/settings/resources",
- icon:
- },
- {
- title: "Clients",
- href: "/{orgId}/settings/clients",
- icon:
- },
- {
- title: "Users & Roles",
- href: "/{orgId}/settings/access",
- icon:
- },
- {
- title: "Shareable Links",
- href: "/{orgId}/settings/share-links",
- icon:
- },
- {
- title: "General",
- href: "/{orgId}/settings/general",
- icon:
- }
-];
-
interface SettingsLayoutProps {
children: React.ReactNode;
params: Promise<{ orgId: string }>;
@@ -74,6 +42,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const getUser = cache(verifySession);
const user = await getUser();
+ const env = pullEnv();
+
if (!user) {
redirect(`/`);
}
@@ -92,7 +62,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const orgUser = await getOrgUser();
if (!orgUser.data.data.isAdmin && !orgUser.data.data.isOwner) {
- throw new Error(t('userErrorNotAdminOrOwner'));
+ throw new Error(t("userErrorNotAdminOrOwner"));
}
} catch {
redirect(`/${params.orgId}`);
@@ -112,6 +82,21 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
}
} catch (e) {}
+ if (env.flags.enableClients) {
+ const existing = orgNavItems.find(
+ (item) => item.title === "sidebarClients"
+ );
+ if (!existing) {
+ const clientsNavItem = {
+ title: "sidebarClients",
+ href: "/{orgId}/settings/clients",
+ icon:
+ };
+
+ orgNavItems.splice(1, 0, clientsNavItem);
+ }
+ }
+
return (
diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx
index 59cccefd..37d12306 100644
--- a/src/app/navigation.tsx
+++ b/src/app/navigation.tsx
@@ -39,11 +39,6 @@ export const orgNavItems: SidebarNavItem[] = [
href: "/{orgId}/settings/resources",
icon:
},
- {
- title: "sidebarClients",
- href: "/{orgId}/settings/clients",
- icon:
- },
{
title: "sidebarAccessControl",
href: "/{orgId}/settings/access",
diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts
index fea07e22..71dcbe68 100644
--- a/src/lib/pullEnv.ts
+++ b/src/lib/pullEnv.ts
@@ -45,7 +45,9 @@ export function pullEnv(): Env {
disableBasicWireguardSites:
process.env.FLAGS_DISABLE_BASIC_WIREGUARD_SITES === "true"
? true
- : false
+ : false,
+ enableClients:
+ process.env.FLAGS_ENABLE_CLIENTS === "true" ? true : false
}
};
}
diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts
index 5e4599aa..ab2dd66c 100644
--- a/src/lib/types/env.ts
+++ b/src/lib/types/env.ts
@@ -24,5 +24,6 @@ export type Env = {
allowBaseDomainResources: boolean;
disableLocalSites: boolean;
disableBasicWireguardSites: boolean;
+ enableClients: boolean;
};
};