mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-26 21:54:38 +02:00
toggle clients with feature flag
This commit is contained in:
parent
7bf9cccbf6
commit
8f1cfd8037
9 changed files with 87 additions and 42 deletions
|
@ -95,6 +95,10 @@ export class Config {
|
||||||
? "true"
|
? "true"
|
||||||
: "false";
|
: "false";
|
||||||
|
|
||||||
|
process.env.FLAGS_ENABLE_CLIENTS = parsedConfig.flags?.disable_clients
|
||||||
|
? "true"
|
||||||
|
: "false";
|
||||||
|
|
||||||
this.rawConfig = parsedConfig;
|
this.rawConfig = parsedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,8 @@ export const configSchema = z
|
||||||
enable_redis: z.boolean().optional(),
|
enable_redis: z.boolean().optional(),
|
||||||
disable_local_sites: z.boolean().optional(),
|
disable_local_sites: z.boolean().optional(),
|
||||||
disable_basic_wireguard_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()
|
.optional()
|
||||||
})
|
})
|
||||||
|
|
29
server/middlewares/verifyClientsEnabled.ts
Normal file
29
server/middlewares/verifyClientsEnabled.ts
Normal file
|
@ -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"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ import { createNewt, getNewtToken } from "./newt";
|
||||||
import { getOlmToken } from "./olm";
|
import { getOlmToken } from "./olm";
|
||||||
import rateLimit from "express-rate-limit";
|
import rateLimit from "express-rate-limit";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
import { verifyClientsEnabled } from "@server/middlewares/verifyClientsEnabled";
|
||||||
|
|
||||||
// Root routes
|
// Root routes
|
||||||
export const unauthenticated = Router();
|
export const unauthenticated = Router();
|
||||||
|
@ -116,6 +117,7 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/pick-client-defaults",
|
"/org/:orgId/pick-client-defaults",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.createClient),
|
verifyUserHasAction(ActionsEnum.createClient),
|
||||||
client.pickClientDefaults
|
client.pickClientDefaults
|
||||||
|
@ -123,6 +125,7 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/clients",
|
"/org/:orgId/clients",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.listClients),
|
verifyUserHasAction(ActionsEnum.listClients),
|
||||||
client.listClients
|
client.listClients
|
||||||
|
@ -130,6 +133,7 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/client/:clientId",
|
"/org/:orgId/client/:clientId",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.getClient),
|
verifyUserHasAction(ActionsEnum.getClient),
|
||||||
client.getClient
|
client.getClient
|
||||||
|
@ -137,6 +141,7 @@ authenticated.get(
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/org/:orgId/client",
|
"/org/:orgId/client",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
verifyUserHasAction(ActionsEnum.createClient),
|
verifyUserHasAction(ActionsEnum.createClient),
|
||||||
client.createClient
|
client.createClient
|
||||||
|
@ -144,6 +149,7 @@ authenticated.put(
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
"/client/:clientId",
|
"/client/:clientId",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyClientAccess,
|
verifyClientAccess,
|
||||||
verifyUserHasAction(ActionsEnum.deleteClient),
|
verifyUserHasAction(ActionsEnum.deleteClient),
|
||||||
client.deleteClient
|
client.deleteClient
|
||||||
|
@ -151,6 +157,7 @@ authenticated.delete(
|
||||||
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/client/:clientId",
|
"/client/:clientId",
|
||||||
|
verifyClientsEnabled,
|
||||||
verifyClientAccess, // this will check if the user has access to the client
|
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
|
verifyUserHasAction(ActionsEnum.updateClient), // this will check if the user has permission to update the client
|
||||||
client.updateClient
|
client.updateClient
|
||||||
|
|
21
src/app/[orgId]/settings/clients/layout.tsx
Normal file
21
src/app/[orgId]/settings/clients/layout.tsx
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -19,7 +19,8 @@ import UserProvider from "@app/providers/UserProvider";
|
||||||
import { Layout } from "@app/components/Layout";
|
import { Layout } from "@app/components/Layout";
|
||||||
import { SidebarNavItem, SidebarNavProps } from "@app/components/SidebarNav";
|
import { SidebarNavItem, SidebarNavProps } from "@app/components/SidebarNav";
|
||||||
import { orgNavItems } from "@app/app/navigation";
|
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";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
@ -28,39 +29,6 @@ export const metadata: Metadata = {
|
||||||
description: ""
|
description: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const topNavItems = [
|
|
||||||
{
|
|
||||||
title: "Sites",
|
|
||||||
href: "/{orgId}/settings/sites",
|
|
||||||
icon: <Combine className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Resources",
|
|
||||||
href: "/{orgId}/settings/resources",
|
|
||||||
icon: <Waypoints className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Clients",
|
|
||||||
href: "/{orgId}/settings/clients",
|
|
||||||
icon: <Workflow className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Users & Roles",
|
|
||||||
href: "/{orgId}/settings/access",
|
|
||||||
icon: <Users className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Shareable Links",
|
|
||||||
href: "/{orgId}/settings/share-links",
|
|
||||||
icon: <LinkIcon className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "General",
|
|
||||||
href: "/{orgId}/settings/general",
|
|
||||||
icon: <Settings className="h-4 w-4" />
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
interface SettingsLayoutProps {
|
interface SettingsLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: Promise<{ orgId: string }>;
|
params: Promise<{ orgId: string }>;
|
||||||
|
@ -74,6 +42,8 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
const getUser = cache(verifySession);
|
const getUser = cache(verifySession);
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
|
|
||||||
|
const env = pullEnv();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect(`/`);
|
redirect(`/`);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +62,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
const orgUser = await getOrgUser();
|
const orgUser = await getOrgUser();
|
||||||
|
|
||||||
if (!orgUser.data.data.isAdmin && !orgUser.data.data.isOwner) {
|
if (!orgUser.data.data.isAdmin && !orgUser.data.data.isOwner) {
|
||||||
throw new Error(t('userErrorNotAdminOrOwner'));
|
throw new Error(t("userErrorNotAdminOrOwner"));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
redirect(`/${params.orgId}`);
|
redirect(`/${params.orgId}`);
|
||||||
|
@ -112,6 +82,21 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} 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: <Workflow className="h-4 w-4" />
|
||||||
|
};
|
||||||
|
|
||||||
|
orgNavItems.splice(1, 0, clientsNavItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProvider user={user}>
|
<UserProvider user={user}>
|
||||||
<Layout orgId={params.orgId} orgs={orgs} navItems={orgNavItems}>
|
<Layout orgId={params.orgId} orgs={orgs} navItems={orgNavItems}>
|
||||||
|
|
|
@ -39,11 +39,6 @@ export const orgNavItems: SidebarNavItem[] = [
|
||||||
href: "/{orgId}/settings/resources",
|
href: "/{orgId}/settings/resources",
|
||||||
icon: <Waypoints className="h-4 w-4" />
|
icon: <Waypoints className="h-4 w-4" />
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "sidebarClients",
|
|
||||||
href: "/{orgId}/settings/clients",
|
|
||||||
icon: <Workflow className="h-4 w-4" />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "sidebarAccessControl",
|
title: "sidebarAccessControl",
|
||||||
href: "/{orgId}/settings/access",
|
href: "/{orgId}/settings/access",
|
||||||
|
|
|
@ -45,7 +45,9 @@ export function pullEnv(): Env {
|
||||||
disableBasicWireguardSites:
|
disableBasicWireguardSites:
|
||||||
process.env.FLAGS_DISABLE_BASIC_WIREGUARD_SITES === "true"
|
process.env.FLAGS_DISABLE_BASIC_WIREGUARD_SITES === "true"
|
||||||
? true
|
? true
|
||||||
: false
|
: false,
|
||||||
|
enableClients:
|
||||||
|
process.env.FLAGS_ENABLE_CLIENTS === "true" ? true : false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,6 @@ export type Env = {
|
||||||
allowBaseDomainResources: boolean;
|
allowBaseDomainResources: boolean;
|
||||||
disableLocalSites: boolean;
|
disableLocalSites: boolean;
|
||||||
disableBasicWireguardSites: boolean;
|
disableBasicWireguardSites: boolean;
|
||||||
|
enableClients: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue