From bb4deb1ae9346254c19bfd46208fe336a2370b91 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 3 Jul 2026 21:05:16 +0200 Subject: [PATCH 01/94] =?UTF-8?q?=E2=9C=A8=20remember=20last=20used=20idp?= =?UTF-8?q?=20in=20dashboard=20login=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoginForm.tsx | 55 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index fe3c80667..40685f846 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -30,10 +30,7 @@ import Link from "next/link"; import { GenerateOidcUrlResponse } from "@server/routers/idp"; import { Separator } from "./ui/separator"; import { useTranslations } from "next-intl"; -import { - generateOidcUrlProxy, - loginProxy -} from "@app/actions/server"; +import { generateOidcUrlProxy, loginProxy } from "@app/actions/server"; import { redirect as redirectTo } from "next/navigation"; import { useEnvContext } from "@app/hooks/useEnvContext"; import IdpTypeIcon from "@app/components/IdpTypeIcon"; @@ -41,6 +38,7 @@ import IdpTypeIcon from "@app/components/IdpTypeIcon"; import { loadReoScript } from "reodotdev"; import { build } from "@server/build"; import MfaInputForm from "@app/components/MfaInputForm"; +import { useLocalStorage } from "@app/hooks/useLocalStorage"; export type LoginFormIDP = { idpId: number; @@ -105,7 +103,6 @@ export default function LoginForm({ } }, []); - const formSchema = z.object({ email: z.string().email({ message: t("emailInvalid") }), password: z.string().min(8, { message: t("passwordRequirementsChars") }) @@ -130,6 +127,10 @@ export default function LoginForm({ } }); + const [lastUsedIdpId, setLastUsedIdpId] = useLocalStorage( + "login:last-used-idp", + null + ); async function onSubmit(values: any) { const { email, password } = form.getValues(); @@ -179,8 +180,7 @@ export default function LoginForm({ if (data.useSecurityKey) { setError( t("securityKeyRequired", { - defaultValue: - "Please use your security key to sign in." + defaultValue: "Please use your security key to sign in." }) ); return; @@ -242,6 +242,8 @@ export default function LoginForm({ async function loginWithIdp(idpId: number) { let redirectUrl: string | undefined; + + setLastUsedIdpId(idpId.toString()); try { const data = await generateOidcUrlProxy( idpId, @@ -356,7 +358,6 @@ export default function LoginForm({ )}
- {!mfaRequested && ( <> { - loginWithIdp(idp.idpId); - }} > - - {idp.name} - + + + {lastUsedIdpId === + idp.idpId.toString() && ( +
+ + {t("idpLastUsed")} + +
+ )} +
); })} )} )} - ); From 05bf77da295aaf2a93c3cfb4dbd5f4161898e949 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 3 Jul 2026 21:05:47 +0200 Subject: [PATCH 02/94] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20remove=20`cache`=20o?= =?UTF-8?q?n=20`verifySession`=20calls=20as=20it's=20already=20wrapped=20i?= =?UTF-8?q?n=20`cache`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/auth/login/page.tsx | 3 +-- src/app/page.tsx | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 274cab561..cdb05c71d 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -29,8 +29,7 @@ export default async function Page(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const searchParams = await props.searchParams; - const getUser = cache(verifySession); - const user = await getUser({ skipCheckVerifyEmail: true }); + const user = await verifySession({ skipCheckVerifyEmail: true }); const isInvite = searchParams?.redirect?.includes("/invite"); const forceLoginParam = searchParams?.forceLogin; diff --git a/src/app/page.tsx b/src/app/page.tsx index 7f0f05b57..84a14cb5c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,6 @@ import UserProvider from "@app/providers/UserProvider"; import { ListUserOrgsResponse } from "@server/routers/org"; import { AxiosResponse } from "axios"; import { redirect } from "next/navigation"; -import { cache } from "react"; import OrganizationLanding from "@app/components/OrganizationLanding"; import { pullEnv } from "@app/lib/pullEnv"; import { cleanRedirect } from "@app/lib/cleanRedirect"; @@ -13,7 +12,6 @@ import { Layout } from "@app/components/Layout"; import RedirectToOrg from "@app/components/RedirectToOrg"; import { InitialSetupCompleteResponse } from "@server/routers/auth"; import { cookies } from "next/headers"; -import { build } from "@server/build"; export const dynamic = "force-dynamic"; @@ -27,8 +25,7 @@ export default async function Page(props: { const env = pullEnv(); - const getUser = cache(verifySession); - const user = await getUser({ skipCheckVerifyEmail: true }); + const user = await verifySession({ skipCheckVerifyEmail: true }); let complete = false; try { From a74c0c227c1df7d3909fe48223f82a05053a85f3 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 3 Jul 2026 21:05:53 +0200 Subject: [PATCH 03/94] =?UTF-8?q?=F0=9F=8C=90=20text?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 1 + 1 file changed, 1 insertion(+) diff --git a/messages/en-US.json b/messages/en-US.json index 4701d4da1..5d774c50b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1503,6 +1503,7 @@ "otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.", "otpAuthSubmit": "Submit Code", "idpContinue": "Or continue with", + "idpLastUsed": "Last used", "otpAuthBack": "Back to Password", "navbar": "Navigation Menu", "navbarDescription": "Main navigation menu for the application", From 289be30e6b17a28f4dd216889e1e39508cae3c50 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 3 Jul 2026 22:33:51 +0200 Subject: [PATCH 04/94] =?UTF-8?q?=E2=9C=A8=20show=20last=20used=20login=20?= =?UTF-8?q?idp=20in=20smart=20login=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/auth/login/page.tsx | 44 ++++++++++++++-- src/components/IdpLoginButtons.tsx | 80 ++++++++++++++++++++---------- src/components/SmartLoginForm.tsx | 15 +++++- src/lib/consts.ts | 1 + src/lib/setClientCookie.ts | 32 ++++++++++++ 5 files changed, 139 insertions(+), 33 deletions(-) create mode 100644 src/lib/consts.ts create mode 100644 src/lib/setClientCookie.ts diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index cdb05c71d..92097512c 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -16,8 +16,11 @@ import LoginCardHeader from "@app/components/LoginCardHeader"; import { priv } from "@app/lib/api"; import { AxiosResponse } from "axios"; import { LoginFormIDP } from "@app/components/LoginForm"; -import { ListIdpsResponse } from "@server/routers/idp"; +import { ListIdpsResponse, type GetIdpResponse } from "@server/routers/idp"; import type { Metadata } from "next"; +import { cookies } from "next/headers"; +import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts"; +import z from "zod"; export const metadata: Metadata = { title: "Log In" @@ -31,6 +34,8 @@ export default async function Page(props: { const searchParams = await props.searchParams; const user = await verifySession({ skipCheckVerifyEmail: true }); + const lastUsedIdpCookie = (await cookies()).get(LAST_USED_IDP_COOKIE_NAME); + const isInvite = searchParams?.redirect?.includes("/invite"); const forceLoginParam = searchParams?.forceLogin; const forceLogin = forceLoginParam === "true"; @@ -84,19 +89,47 @@ export default async function Page(props: { (build === "enterprise" && env.app.identityProviderMode === "org"); let loginIdps: LoginFormIDP[] = []; + let lastUsedIdpForSmartLogin: (LoginFormIDP & { orgId?: string }) | null = + null; if (!useSmartLogin) { // Load IdPs for DashboardLoginForm (OSS or org-only IdP mode) if (build === "oss" || env.app.identityProviderMode !== "org") { - const idpsRes = await cache( - async () => - await priv.get>("/idp") - )(); + const idpsRes = + await priv.get>("/idp"); loginIdps = idpsRes.data.data.idps.map((idp) => ({ idpId: idp.idpId, name: idp.name, variant: idp.type })) as LoginFormIDP[]; } + } else { + if (lastUsedIdpCookie) { + const lastUsedIdpSchema = z.object({ + orgId: z.string().optional(), + idpId: z.number() + }); + try { + const persistedData = lastUsedIdpSchema.parse( + JSON.parse(lastUsedIdpCookie.value) + ); + + const idpRes = await priv.get>( + `/idp/${persistedData.idpId}` + ); + + const idp = idpRes.data.data.idp; + + lastUsedIdpForSmartLogin = { + idpId: idp.idpId, + name: idp.name, + variant: idp.type, + orgId: persistedData.orgId, + lastUsed: true + }; + } catch (error) { + // the idp might not exist or the data is malformatted, skip this + } + } } const t = await getTranslations(); @@ -159,6 +192,7 @@ export default async function Page(props: { redirect={redirectUrl} forceLogin={forceLogin} defaultUser={defaultUser} + lastUsedIdp={lastUsedIdpForSmartLogin} orgSignIn={ !isInvite && (build === "saas" || diff --git a/src/components/IdpLoginButtons.tsx b/src/components/IdpLoginButtons.tsx index d015dce52..0851a0dd0 100644 --- a/src/components/IdpLoginButtons.tsx +++ b/src/components/IdpLoginButtons.tsx @@ -1,26 +1,25 @@ "use client"; -import { useEffect, useState } from "react"; -import { Button } from "@app/components/ui/button"; -import { Alert, AlertDescription } from "@app/components/ui/alert"; -import { useTranslations } from "next-intl"; +import { generateOidcUrlProxy } from "@app/actions/server"; import IdpTypeIcon from "@app/components/IdpTypeIcon"; -import { - generateOidcUrlProxy, - type GenerateOidcUrlResponse -} from "@app/actions/server"; +import { Alert, AlertDescription } from "@app/components/ui/alert"; +import { Button } from "@app/components/ui/button"; +import { cleanRedirect } from "@app/lib/cleanRedirect"; +import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts"; +import { setClientCookie } from "@app/lib/setClientCookie"; +import { useTranslations } from "next-intl"; import { redirect as redirectTo, - useParams, + useRouter, useSearchParams } from "next/navigation"; -import { useRouter } from "next/navigation"; -import { cleanRedirect } from "@app/lib/cleanRedirect"; +import { useEffect, useState, useTransition } from "react"; export type LoginFormIDP = { idpId: number; name: string; variant?: string; + lastUsed?: boolean; }; type IdpLoginButtonsProps = { @@ -35,7 +34,6 @@ export default function IdpLoginButtons({ orgId }: IdpLoginButtonsProps) { const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); const t = useTranslations(); const params = useSearchParams(); @@ -52,10 +50,22 @@ export default function IdpLoginButtons({ } }, []); + const [loading, startTransition] = useTransition(); + async function loginWithIdp(idpId: number) { - setLoading(true); setError(null); + setClientCookie( + LAST_USED_IDP_COOKIE_NAME, + JSON.stringify({ + orgId, + idpId + }), + { + sameSite: "Lax" + } + ); + let redirectToUrl: string | undefined; try { console.log("generating", idpId, redirect || "/", orgId); @@ -68,7 +78,6 @@ export default function IdpLoginButtons({ if (response.error) { setError(response.message); - setLoading(false); return; } @@ -84,7 +93,6 @@ export default function IdpLoginButtons({ "An unexpected error occurred. Please try again." }) ); - setLoading(false); } if (redirectToUrl) { @@ -124,20 +132,38 @@ export default function IdpLoginButtons({ idp.variant || idp.name.toLowerCase(); return ( - + + + {idp.lastUsed && ( +
+ + {t("idpLastUsed")} + +
+ )} + ); })} diff --git a/src/components/SmartLoginForm.tsx b/src/components/SmartLoginForm.tsx index dd0e131df..de1955771 100644 --- a/src/components/SmartLoginForm.tsx +++ b/src/components/SmartLoginForm.tsx @@ -27,6 +27,8 @@ import UserProfileCard from "@app/components/UserProfileCard"; import SecurityKeyAuthButton from "@app/components/SecurityKeyAuthButton"; import { Separator } from "@app/components/ui/separator"; import OrgSignInLink from "@app/components/OrgSignInLink"; +import type { LoginFormIDP } from "./LoginForm"; +import IdpLoginButtons from "./IdpLoginButtons"; const identifierSchema = z.object({ identifier: z.string().min(1, "Username or email is required") @@ -53,6 +55,7 @@ type SmartLoginFormProps = { forceLogin?: boolean; defaultUser?: string; orgSignIn?: OrgSignInConfig; + lastUsedIdp?: (LoginFormIDP & { orgId?: string }) | null; }; type ViewState = @@ -89,7 +92,8 @@ export default function SmartLoginForm({ redirect, forceLogin, defaultUser, - orgSignIn + orgSignIn, + lastUsedIdp }: SmartLoginFormProps) { const router = useRouter(); const { env } = useEnvContext(); @@ -294,6 +298,15 @@ export default function SmartLoginForm({ + + {lastUsedIdp && ( + + )} + Date: Fri, 3 Jul 2026 22:33:58 +0200 Subject: [PATCH 05/94] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20fix=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoginForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 40685f846..eef7406a7 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -44,6 +44,7 @@ export type LoginFormIDP = { idpId: number; name: string; variant?: string; + lastUsed?: boolean; }; type LoginFormProps = { From 2fe85ebda2d43c543d13b9c5e6730e7649e05bfb Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 8 Jul 2026 03:11:12 +0200 Subject: [PATCH 06/94] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20set=20last=20IDP=20t?= =?UTF-8?q?o=20null=20if=20login=20with=20email=20+=20password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoginForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index eef7406a7..82f948ca9 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -137,6 +137,7 @@ export default function LoginForm({ const { email, password } = form.getValues(); const { code } = mfaForm.getValues(); + setLastUsedIdpId(null); setLoading(true); setError(null); From 34d5c9535d3a9c4f029088cf2a4db905bd3ab2ab Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 10 Jul 2026 03:22:28 +0200 Subject: [PATCH 07/94] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20delete=20last=20used?= =?UTF-8?q?=20IDP=20coookie=20when=20logging=20in=20with=20email/password?= =?UTF-8?q?=20on=20smart=20login=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoginPasswordForm.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/LoginPasswordForm.tsx b/src/components/LoginPasswordForm.tsx index d6e0c0b79..efbc8d727 100644 --- a/src/components/LoginPasswordForm.tsx +++ b/src/components/LoginPasswordForm.tsx @@ -22,6 +22,8 @@ import Link from "next/link"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import MfaInputForm from "@app/components/MfaInputForm"; +import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts"; +import { setClientCookie } from "@app/lib/setClientCookie"; type LoginPasswordFormProps = { identifier: string; @@ -82,6 +84,12 @@ export default function LoginPasswordForm({ const { password } = values; const { code } = mfaForm.getValues(); + // delete last used auth cookie by setting it in the past + setClientCookie(LAST_USED_IDP_COOKIE_NAME, JSON.stringify(null), { + sameSite: "Lax", + days: -1 + }); + setLoading(true); setError(null); From 8d018fe47da9ea50d5ab44e28d2679dfb74524bb Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 9 Jul 2026 21:28:48 -0400 Subject: [PATCH 08/94] Clean up --- server/lib/blueprints/applyBlueprint.ts | 6 ------ server/lib/blueprints/privateResources.ts | 3 --- server/routers/siteResource/createSiteResource.ts | 3 --- 3 files changed, 12 deletions(-) diff --git a/server/lib/blueprints/applyBlueprint.ts b/server/lib/blueprints/applyBlueprint.ts index c62dd4735..44646e0bf 100644 --- a/server/lib/blueprints/applyBlueprint.ts +++ b/server/lib/blueprints/applyBlueprint.ts @@ -34,12 +34,6 @@ import { rebuildClientAssociationsFromSiteResource, waitForSiteResourceRebuildIdle } from "../rebuildClientAssociations"; -import { build } from "@server/build"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import next from "next"; -import { LimitId } from "../billing"; -import { usageService } from "../billing/usageService"; type ApplyBlueprintArgs = { orgId: string; diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index 74a1f618f..aa146679f 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -26,9 +26,6 @@ import { createCertificate } from "#dynamic/routers/certificates/createCertifica import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { tierMatrix } from "../billing/tierMatrix"; import { build } from "@server/build"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import next from "next"; import { LimitId } from "../billing"; import { usageService } from "../billing/usageService"; diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index eaf4cf130..365053cf1 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -56,7 +56,6 @@ const createSiteResourceSchema = z siteId: z.number().int().positive().optional(), // DEPRECATED: for backward compatibility, we will convert this to siteIds array if provided destinationPort: z.int().positive().optional(), destination: z.string().min(1).nullish(), - enabled: z.boolean().default(true), alias: z .string() .regex( @@ -275,7 +274,6 @@ export async function createSiteResource( scheme, destinationPort, destination, - enabled, ssl, alias, userIds, @@ -539,7 +537,6 @@ export async function createSiteResource( destination: destination, // the ssh can be null scheme, destinationPort, - enabled, alias: alias ? alias.trim() : null, aliasAddress, tcpPortRangeString: tcpPortRangeStringAdjusted, From d38f9ac2bb46ceaf720d97a059bf7d91098f5237 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 9 Jul 2026 21:32:35 -0400 Subject: [PATCH 09/94] update readme --- README.md | 42 +++++++++++++++++++++-- public/screenshots/resource-launcher.png | Bin 0 -> 568945 bytes 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 public/screenshots/resource-launcher.png diff --git a/README.md b/README.md index ab857350e..1827f2570 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@

-Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources with NAT traversal, all with granular access controls. +Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure connectivity to infrastructure anywhere. It combines reverse-proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to private resources with NAT traversal, all with granular access control. ## Installation @@ -63,11 +63,26 @@ Pangolin is an open-source, identity-based remote access platform built on WireG Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform. +* Lightweight user-space connector runs anywhere +* Punches through any firewall +* Doesn't require open ports or a public IP +* Strict network segmentation +* WireGuard-based +* Get alerts when a device or network resource goes down + Sites ### Browser-based reverse proxy access -Expose web applications through identity and context-aware tunneled reverse proxies. Users access applications through any web browser with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. +Expose HTTPS web applications and connect to VNC, RDP, and SSH entirely in the browser through identity and context-aware tunneled reverse proxies. Users access resources with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. + +* Expose a web panel anywhere +* Access via any web browser +* Single sign-on across all resources +* HTTPS resources +* Remote desktop in the browser with VNC and RDP +* In-browser SSH terminal with privileged access management (PAM) +* PIN codes, passcodes, email OTP, geoblocking, allow-lists, and more Reverse proxy access @@ -75,14 +90,35 @@ Expose web applications through identity and context-aware tunneled reverse prox Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network. +* Peer-to-peer with intelligent NAT traversal +* Hosts/IPs and port ranges +* Network ranges/CIDRs +* Friendly DNS aliases for network addresses +* Privileged access management (PAM) with SSH resources +* Private HTTPS resources only accessible on the private network + Private resources ### Give users and roles access to resources -Use Pangolin's built in users or bring your own identity provider and set up role based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define. +Use Pangolin's built-in users or bring your own identity provider and set up role-based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define. + +* Bring your existing identity provider (IdP) or use Pangolin identities +* Sync users and roles from your IdP +* User- and role-based access control +* Full network audit and access logs Users from identity provider with roles +### Find and launch resources from a personalized home page + +Give users a landing page to quickly find and open the resources they can access. Resources are grouped by site or label, searchable, and filterable, with grid or list views. Saved views capture filters, grouping, and layout as personal or organization-wide defaults. + +* Single place for admins and non-admins to see accessible resources +* Create reusable views for common access patterns + +Resource Launcher + ## Download Clients Download the Pangolin client for your platform: diff --git a/public/screenshots/resource-launcher.png b/public/screenshots/resource-launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..93b803ee66d236eba80036a4120024b1cd2fe767 GIT binary patch literal 568945 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%0D(Fn|6li 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
nUjW-p!+Uh+;iDe3xA*V&_|O^{h`f9J@7|Er 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 z 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�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%0Adclm!s11Kz&ZViT~SyODT(EFh?HhpcKFnOAO&D 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

WnA%MSBm0k_74nX6`EOQ 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 jm;A#W<32CQ@zlhJKUn Date: Thu, 9 Jul 2026 21:42:59 -0400 Subject: [PATCH 10/94] Filter out disabled --- server/lib/ip.ts | 9 +++++ server/lib/rebuildClientAssociations.ts | 46 +++++++++++++++-------- server/routers/newt/buildConfiguration.ts | 7 +++- server/routers/olm/buildConfiguration.ts | 10 ++++- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 56576282a..fb161df1c 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -496,6 +496,7 @@ export function generateRemoteSubnets( ): string[] { const remoteSubnets = allSiteResources .filter((sr) => { + if (!sr.enabled) return false; if (!sr.destination) return false; if (sr.mode === "cidr") { @@ -530,6 +531,7 @@ export function generateAliasConfig(allSiteResources: SiteResource[]): Alias[] { return allSiteResources .filter( (sr) => + sr.enabled && sr.aliasAddress && ((sr.alias && (sr.mode == "host" || sr.mode == "ssh")) || (sr.fullDomain && sr.mode == "http")) @@ -662,6 +664,13 @@ export async function generateSubnetProxyTargetV2( subnet: string | null; }[] ): Promise { + if (!siteResource.enabled) { + logger.debug( + `Site resource ${siteResource.siteResourceId} is disabled, skipping target generation.` + ); + return; + } + if (clients.length === 0) { logger.debug( `No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.` diff --git a/server/lib/rebuildClientAssociations.ts b/server/lib/rebuildClientAssociations.ts index efb856825..eae579634 100644 --- a/server/lib/rebuildClientAssociations.ts +++ b/server/lib/rebuildClientAssociations.ts @@ -1561,9 +1561,19 @@ export async function handleMessagingForUpdatedSiteResource( updatedSiteResource.udpPortRangeString || existingSiteResource.disableIcmp !== updatedSiteResource.disableIcmp); + // Toggling enabled on/off doesn't change any of the fields above, but it + // does change whether targets/peer data should exist at all, so it needs + // to drive the same old->new diff machinery: going enabled->disabled + // diffs "real data" against "nothing" (a remove), and disabled->enabled + // diffs "nothing" against "real data" (an add). generateSubnetProxyTargetV2/ + // generateRemoteSubnets/generateAliasConfig already return nothing for a + // disabled resource, so no other changes are needed here. + const enabledChanged = + existingSiteResource && + existingSiteResource.enabled !== updatedSiteResource.enabled; logger.debug( - `handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)}` + `handleMessagingForUpdatedSiteResource: change flags destinationChanged=${Boolean(destinationChanged)} destinationPortChanged=${Boolean(destinationPortChanged)} aliasChanged=${Boolean(aliasChanged)} fullDomainChanged=${Boolean(fullDomainChanged)} sslChanged=${Boolean(sslChanged)} portRangesChanged=${Boolean(portRangesChanged)} enabledChanged=${Boolean(enabledChanged)}` ); // if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all @@ -1574,14 +1584,16 @@ export async function handleMessagingForUpdatedSiteResource( fullDomainChanged || sslChanged || portRangesChanged || - destinationPortChanged + destinationPortChanged || + enabledChanged ) { const shouldUpdateTargets = destinationChanged || sslChanged || portRangesChanged || fullDomainChanged || - destinationPortChanged; + destinationPortChanged || + enabledChanged; logger.debug( `handleMessagingForUpdatedSiteResource: entering unchanged-site update path shouldUpdateTargets=${shouldUpdateTargets}` @@ -1657,20 +1669,22 @@ export async function handleMessagingForUpdatedSiteResource( peerDataUpdateBatch.push({ clientId: client.clientId, siteId, - remoteSubnets: destinationChanged - ? { - oldRemoteSubnets: !oldDestinationStillInUseBySite - ? generateRemoteSubnets([ - existingSiteResource - ]) - : [], - newRemoteSubnets: generateRemoteSubnets([ - updatedSiteResource - ]) - } - : undefined, + remoteSubnets: + destinationChanged || enabledChanged + ? { + oldRemoteSubnets: + !oldDestinationStillInUseBySite + ? generateRemoteSubnets([ + existingSiteResource + ]) + : [], + newRemoteSubnets: generateRemoteSubnets([ + updatedSiteResource + ]) + } + : undefined, aliases: - aliasChanged || fullDomainChanged // the full domain is sent down as an alias + aliasChanged || fullDomainChanged || enabledChanged // the full domain is sent down as an alias ? { oldAliases: generateAliasConfig([ existingSiteResource diff --git a/server/routers/newt/buildConfiguration.ts b/server/routers/newt/buildConfiguration.ts index 5b7f211ae..a12d035a5 100644 --- a/server/routers/newt/buildConfiguration.ts +++ b/server/routers/newt/buildConfiguration.ts @@ -148,7 +148,12 @@ export async function buildClientConfigurationForNewtClient( .from(siteResources) .innerJoin(networks, eq(siteResources.networkId, networks.networkId)) .innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId)) - .where(eq(siteNetworks.siteId, siteId)) + .where( + and( + eq(siteNetworks.siteId, siteId), + eq(siteResources.enabled, true) + ) + ) .then((rows) => rows.map((r) => r.siteResources)); const targetsToSend: SubnetProxyTargetV2[] = []; diff --git a/server/routers/olm/buildConfiguration.ts b/server/routers/olm/buildConfiguration.ts index f72731c92..0266fa041 100644 --- a/server/routers/olm/buildConfiguration.ts +++ b/server/routers/olm/buildConfiguration.ts @@ -16,7 +16,7 @@ import { generateRemoteSubnets } from "@server/lib/ip"; import logger from "@server/logger"; -import { eq, inArray } from "drizzle-orm"; +import { and, eq, inArray } from "drizzle-orm"; import { addPeer, deletePeer } from "../newt/peers"; import config from "@server/lib/config"; @@ -70,7 +70,13 @@ export async function buildSiteConfigurationForOlmClient( .innerJoin(networks, eq(siteResources.networkId, networks.networkId)) .innerJoin(siteNetworks, eq(networks.networkId, siteNetworks.networkId)) .where( - eq(clientSiteResourcesAssociationsCache.clientId, client.clientId) + and( + eq( + clientSiteResourcesAssociationsCache.clientId, + client.clientId + ), + eq(siteResources.enabled, true) + ) ); const siteResourcesBySiteId = new Map(); From 0b2693a317ba9dc788db3a44cc40e25423defc1b Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 09:59:11 -0400 Subject: [PATCH 11/94] Add toggle to the ui for enabled --- .../private/[niceId]/general/page.tsx | 44 ++++++- src/components/PrivateResourcesTable.tsx | 109 +++++++++++++++++- src/lib/privateResourceForm.ts | 3 +- 3 files changed, 152 insertions(+), 4 deletions(-) diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx index 6e1a9fdeb..d14b9c3d5 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx @@ -16,12 +16,14 @@ import { Button } from "@app/components/ui/button"; import { Form, FormControl, + FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; +import { SwitchInput } from "@app/components/SwitchInput"; import { createGeneralFormSchema } from "@app/lib/privateResourceForm"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslations } from "next-intl"; @@ -41,7 +43,8 @@ export default function PrivateResourceGeneralPage() { resolver: zodResolver(formSchema), defaultValues: { name: siteResource.name, - niceId: siteResource.niceId + niceId: siteResource.niceId, + enabled: siteResource.enabled } }); @@ -52,7 +55,8 @@ export default function PrivateResourceGeneralPage() { const data = form.getValues(); await save({ name: data.name, - niceId: data.niceId + niceId: data.niceId, + enabled: data.enabled }); }, null); @@ -76,6 +80,42 @@ export default function PrivateResourceGeneralPage() { id="private-resource-general-form" > + + ( + + + + form.setValue( + "enabled", + val + ) + } + /> + + + {t( + "disabledResourceDescription" + )} + + + + )} + /> + + >( + `site-resource/${resourceId}`, + { + enabled: val + } + ); + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: t("resourcesErrorUpdate"), + description: formatAxiosError( + e, + t("resourcesErrorUpdateDescription") + ) + }); + } + } + const deleteInternalResource = async ( resourceId: number, siteId: number @@ -429,6 +470,36 @@ export default function PrivateResourcesTable({ ); } }, + { + accessorKey: "enabled", + friendlyName: t("enabled"), + header: () => ( + + handleFilterChange("enabled", value) + } + searchPlaceholder={t("searchPlaceholder")} + emptyMessage={t("emptySearchOptions")} + label={t("enabled")} + className="p-3" + /> + ), + cell: ({ row }) => ( + + ) + }, { id: "labels", accessorKey: "labels", @@ -643,3 +714,39 @@ function ClientResourceLabelCell({ /> ); } + +type InternalResourceEnabledFormProps = { + resource: InternalResourceRow; + onToggleInternalResourceEnabled: ( + val: boolean, + resourceId: number + ) => Promise; +}; + +function InternalResourceEnabledForm({ + resource, + onToggleInternalResourceEnabled +}: InternalResourceEnabledFormProps) { + const [optimisticEnabled, setOptimisticEnabled] = useOptimistic( + resource.enabled + ); + + const formRef = useRef>(null); + + async function submitAction(formData: FormData) { + const newEnabled = !(formData.get("enabled") === "on"); + setOptimisticEnabled(newEnabled); + await onToggleInternalResourceEnabled(newEnabled, resource.id); + } + + return ( +

+ formRef.current?.requestSubmit()} + /> + + ); +} diff --git a/src/lib/privateResourceForm.ts b/src/lib/privateResourceForm.ts index d9f6fd691..86c6228cd 100644 --- a/src/lib/privateResourceForm.ts +++ b/src/lib/privateResourceForm.ts @@ -342,7 +342,8 @@ export function createGeneralFormSchema(t: TranslateFn) { .string() .min(1) .max(255) - .regex(/^[a-zA-Z0-9-]+$/) + .regex(/^[a-zA-Z0-9-]+$/), + enabled: z.boolean() }); } From f4bee6406a23250fd5f64fb6cad44c5dca1959b3 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 10:11:47 -0400 Subject: [PATCH 12/94] Support partial updates --- server/routers/siteResource/updateSiteResource.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 173a15190..e1e25f918 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -152,6 +152,11 @@ const updateSiteResourceSchema = z ) .refine( (data) => { + // this is a partial update; only enforce destination when the + // caller is actually changing mode or destination + if (data.mode === undefined && data.destination === undefined) { + return true; + } // destination is only optional for ssh mode with native authDaemonMode if (data.mode === "ssh" && data.authDaemonMode === "native") { return true; From 538b57941c114307528098a2ef4c95e07c5a3b50 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 11:32:27 -0400 Subject: [PATCH 13/94] Makes sure patch works z# --- .../siteResource/updateSiteResource.ts | 30 +++++++++++++++---- src/lib/privateResourceForm.ts | 1 - 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index e1e25f918..0ccf29908 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -416,8 +416,10 @@ export async function updateSiteResource( : []; const existingSiteIds = existingSiteNetworks.map((sn) => sn.siteId); - let fullDomain: string | null = null; - let finalSubdomain: string | null = null; + // undefined means "leave unchanged" (partial update); only nulled out + // when the mode is explicitly being changed away from http + let fullDomain: string | null | undefined = undefined; + let finalSubdomain: string | null | undefined = undefined; if (domainId) { // Validate domain and construct full domain const domainResult = await validateAndConstructDomain( @@ -453,6 +455,11 @@ export async function updateSiteResource( ) ); } + } else if (mode !== undefined && mode !== "http") { + // mode is explicitly changing away from http, so the resource + // can no longer have a domain associated with it + fullDomain = null; + finalSubdomain = null; } // make sure the alias is unique within the org if provided @@ -521,15 +528,28 @@ export async function updateSiteResource( destination: destination, destinationPort: destinationPort, enabled: enabled, - alias: alias ? alias.trim() : null, + alias: + alias !== undefined + ? alias + ? alias.trim() + : null + : mode !== undefined && + mode !== "host" && + mode !== "ssh" + ? null + : undefined, tcpPortRangeString: tcpPortRangeStringAdjusted, udpPortRangeString: mode == "http" || mode == "ssh" ? "" : udpPortRangeString, disableIcmp: - disableIcmp || - (mode == "http" || mode == "ssh" ? true : false), + mode !== undefined + ? disableIcmp || + (mode == "http" || mode == "ssh" + ? true + : false) + : disableIcmp, domainId, subdomain: finalSubdomain, fullDomain, diff --git a/src/lib/privateResourceForm.ts b/src/lib/privateResourceForm.ts index 86c6228cd..3edd436e4 100644 --- a/src/lib/privateResourceForm.ts +++ b/src/lib/privateResourceForm.ts @@ -165,7 +165,6 @@ export function buildCreateSiteResourcePayload( siteIds: data.siteIds, mode: data.mode, destination: isNativeSsh ? undefined : (data.destination ?? undefined), - enabled: true, ...(data.mode === "http" && { scheme: data.scheme, ssl: data.ssl ?? false, From cf9a17cc2ef6fb6f292e0f9379334d8afb12cec4 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 15:03:50 -0400 Subject: [PATCH 14/94] Add status filter --- server/db/pg/schema/schema.ts | 10 +++++-- server/db/sqlite/schema/schema.ts | 6 +++-- server/routers/resource/listResources.ts | 14 ++++++++++ .../resource/listUserResourceAliases.ts | 26 ++++++++++++++---- .../siteResource/listAllSiteResourcesByOrg.ts | 14 ++++++++++ .../routers/siteResource/listSiteResources.ts | 27 ++++++++++++++----- 6 files changed, 81 insertions(+), 16 deletions(-) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index b207b423b..f3375b0d9 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -200,7 +200,10 @@ export const resources = pgTable( authDaemonMode: varchar("authDaemonMode", { length: 32 }) .$type<"site" | "remote" | "native">() .default("site"), - authDaemonPort: integer("authDaemonPort").default(22123) + authDaemonPort: integer("authDaemonPort").default(22123), + status: varchar("status") + .$type<"pending" | "approved">() + .default("approved") }, (t) => [ index("idx_resources_fulldomain") @@ -451,7 +454,10 @@ export const siteResources = pgTable( onDelete: "set null" }), subdomain: varchar("subdomain"), - fullDomain: varchar("fullDomain") + fullDomain: varchar("fullDomain"), + status: varchar("status") + .$type<"pending" | "approved">() + .default("approved") }, (t) => [index("idx_siteresources_orgid_niceid").on(t.orgId, t.niceId)] ); diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 4ec4916b5..3fcf38a41 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -209,7 +209,8 @@ export const resources = sqliteTable("resources", { authDaemonMode: text("authDaemonMode") .$type<"site" | "remote" | "native">() .default("site"), - authDaemonPort: integer("authDaemonPort").default(22123) + authDaemonPort: integer("authDaemonPort").default(22123), + status: text("status").$type<"pending" | "approved">().default("approved") }); export const labels = sqliteTable("labels", { @@ -447,7 +448,8 @@ export const siteResources = sqliteTable("siteResources", { onDelete: "set null" }), subdomain: text("subdomain"), - fullDomain: text("fullDomain") + fullDomain: text("fullDomain"), + status: text("status").$type<"pending" | "approved">().default("approved") }); export const networks = sqliteTable("networks", { diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 9567b5bcd..e0baa49b1 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -138,6 +138,15 @@ const listResourcesSchema = z.strictObject({ description: "When set, only resources that have at least one target on this site are returned" }), + status: z + .enum(["pending", "approved"]) + .optional() + .catch(undefined) + .openapi({ + type: "string", + enum: ["pending", "approved"], + description: "Filter by resource status" + }), labels: z .preprocess((val) => { if (val === undefined || val === null || val === "") { @@ -451,6 +460,7 @@ export async function listResources( sort_by, order, siteId, + status, labels: labelFilter } = parsedQuery.data; @@ -660,6 +670,10 @@ export async function listResources( } } + if (typeof status !== "undefined") { + conditions.push(eq(resources.status, status)); + } + if (siteId != null) { const resourcesWithSite = db .select({ resourceId: targets.resourceId }) diff --git a/server/routers/resource/listUserResourceAliases.ts b/server/routers/resource/listUserResourceAliases.ts index 4ec87b8bb..100b1624d 100644 --- a/server/routers/resource/listUserResourceAliases.ts +++ b/server/routers/resource/listUserResourceAliases.ts @@ -45,11 +45,12 @@ function userResourceAliasesCacheKey( page: number, pageSize: number, includeLabels: boolean, - labelFilter: string[] + labelFilter: string[], + status?: "pending" | "approved" ) { const labelsKey = labelFilter.length > 0 ? labelFilter.slice().sort().join(",") : "all"; - return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}`; + return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}:${status ?? "all"}`; } const listUserResourceAliasesParamsSchema = z.strictObject({ @@ -96,7 +97,16 @@ const listUserResourceAliasesQuerySchema = z.strictObject({ type: "array", description: "Filter by resource labels. A resource matches when it has any of the given labels (OR)." - }) + }), + status: z + .enum(["pending", "approved"]) + .optional() + .catch(undefined) + .openapi({ + type: "string", + enum: ["pending", "approved"], + description: "Filter by site resource status" + }) }); export type UserResourceAliasItem = { @@ -130,7 +140,8 @@ export async function listUserResourceAliases( page, pageSize, includeLabels, - labels: labelFilter + labels: labelFilter, + status } = parsedQuery.data; const parsedParams = listUserResourceAliasesParamsSchema.safeParse( @@ -172,7 +183,8 @@ export async function listUserResourceAliases( page, pageSize, includeLabels, - labelFilter ?? [] + labelFilter ?? [], + status ); const cachedData: ListUserResourceAliasesResponse | undefined = await cache.get(cacheKey); @@ -257,6 +269,10 @@ export async function listUserResourceAliases( inArray(siteResources.siteResourceId, accessibleSiteResourceIds) ]; + if (typeof status !== "undefined") { + whereConditions.push(eq(siteResources.status, status)); + } + if (labelFilter && labelFilter.length > 0) { whereConditions.push( inArray( diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 4515e033e..2cbbc2c4e 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -86,6 +86,15 @@ const listAllSiteResourcesByOrgQuerySchema = z.strictObject({ description: "When set, only site resources associated with this site (via network) are returned" }), + status: z + .enum(["pending", "approved"]) + .optional() + .catch(undefined) + .openapi({ + type: "string", + enum: ["pending", "approved"], + description: "Filter by site resource status" + }), labels: z .preprocess((val) => { if (val === undefined || val === null || val === "") { @@ -283,6 +292,7 @@ export async function listAllSiteResourcesByOrg( sort_by, order, siteId, + status, labels: labelFilter } = parsedQuery.data; @@ -315,6 +325,10 @@ export async function listAllSiteResourcesByOrg( conditions.push(eq(siteResources.mode, mode)); } + if (typeof status !== "undefined") { + conditions.push(eq(siteResources.status, status)); + } + if (labelFilter && labelFilter.length > 0) { conditions.push( inArray( diff --git a/server/routers/siteResource/listSiteResources.ts b/server/routers/siteResource/listSiteResources.ts index a9688a9c6..c97900bec 100644 --- a/server/routers/siteResource/listSiteResources.ts +++ b/server/routers/siteResource/listSiteResources.ts @@ -47,6 +47,15 @@ const listSiteResourcesQuerySchema = z.strictObject({ enum: ["asc", "desc"], default: "asc", description: "Sort order" + }), + status: z + .enum(["pending", "approved"]) + .optional() + .catch(undefined) + .openapi({ + type: "string", + enum: ["pending", "approved"], + description: "Filter by site resource status" }) }); @@ -110,7 +119,7 @@ export async function listSiteResources( } const { siteId, orgId } = parsedParams.data; - const { limit, offset, sort_by, order } = parsedQuery.data; + const { limit, offset, sort_by, order, status } = parsedQuery.data; // Verify the site exists and belongs to the org const site = await db @@ -124,6 +133,15 @@ export async function listSiteResources( } // Get site resources by joining networks to siteResources via siteNetworks + const conditions = [ + eq(siteNetworks.siteId, siteId), + eq(siteResources.orgId, orgId) + ]; + + if (typeof status !== "undefined") { + conditions.push(eq(siteResources.status, status)); + } + const siteResourcesList = await db .select() .from(siteNetworks) @@ -132,12 +150,7 @@ export async function listSiteResources( siteResources, eq(siteResources.networkId, networks.networkId) ) - .where( - and( - eq(siteNetworks.siteId, siteId), - eq(siteResources.orgId, orgId) - ) - ) + .where(and(...conditions)) .orderBy( sort_by ? order === "asc" From 609fb357bb1e2ec26535c202181beb4112d70fa8 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 15:16:50 -0400 Subject: [PATCH 15/94] Support enable in the blueprints --- server/lib/blueprints/privateResources.ts | 6 ++---- server/lib/blueprints/types.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index aa146679f..7d5d9a874 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -240,8 +240,7 @@ export async function updatePrivateResources( scheme: resourceData.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], - enabled: true, // hardcoded for now - // enabled: resourceData.enabled ?? true, + enabled: resourceData.enabled ?? true, alias: resourceData.alias || null, disableIcmp: resourceData["disable-icmp"] || @@ -493,8 +492,7 @@ export async function updatePrivateResources( scheme: resourceData.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], - enabled: true, // hardcoded for now - // enabled: resourceData.enabled ?? true, + enabled: resourceData.enabled ?? true, alias: resourceData.alias || null, aliasAddress: aliasAddress, disableIcmp: diff --git a/server/lib/blueprints/types.ts b/server/lib/blueprints/types.ts index 5495a2d8e..cc9865533 100644 --- a/server/lib/blueprints/types.ts +++ b/server/lib/blueprints/types.ts @@ -470,7 +470,7 @@ export const PrivateResourceSchema = z // proxyPort: z.int().positive().optional(), "destination-port": z.int().positive().optional(), destination: z.string().min(1).optional(), - // enabled: z.boolean().default(true), + enabled: z.boolean().default(true), "tcp-ports": portRangeStringSchema.optional().default("*"), "udp-ports": portRangeStringSchema.optional().default("*"), "disable-icmp": z.boolean().optional().default(false), From 14680df160dead2fc2aae1259327f24bcbe314a3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 15:23:09 -0400 Subject: [PATCH 16/94] suse tiermatrix in server component susbcribed check --- src/app/auth/org/[orgId]/page.tsx | 25 +++++++++++++------ src/app/auth/org/page.tsx | 6 ++++- src/app/auth/resource/[resourceGuid]/page.tsx | 25 +++++++++++++++---- src/lib/api/isOrgSubscribed.ts | 9 +++++-- src/lib/loadOrgLoginPageBranding.ts | 6 ++++- 5 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/app/auth/org/[orgId]/page.tsx b/src/app/auth/org/[orgId]/page.tsx index 2329d5b7c..b2a225120 100644 --- a/src/app/auth/org/[orgId]/page.tsx +++ b/src/app/auth/org/[orgId]/page.tsx @@ -13,6 +13,8 @@ import { redirect } from "next/navigation"; import OrgLoginPage from "@app/components/OrgLoginPage"; import { pullEnv } from "@app/lib/pullEnv"; import type { Metadata } from "next"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed"; export const metadata: Metadata = { title: "Organization Login" @@ -68,15 +70,22 @@ export default async function OrgAuthPage(props: { variant: idp.variant })) as LoginFormIDP[]; + const hasLoginPageBranding = await isOrgSubscribed( + orgId, + tierMatrix.loginPageBranding + ); + let branding: LoadLoginPageBrandingResponse | null = null; - try { - const res = await priv.get< - AxiosResponse - >(`/login-page-branding?orgId=${orgId}`); - if (res.status === 200) { - branding = res.data.data; - } - } catch (error) {} + if (hasLoginPageBranding) { + try { + const res = await priv.get< + AxiosResponse + >(`/login-page-branding?orgId=${orgId}`); + if (res.status === 200) { + branding = res.data.data; + } + } catch (error) {} + } return ( { + const wildcardMatchesRedirect = ( + wildcardDomain: string, + host: string + ): boolean => { if (!wildcardDomain.startsWith("*.")) return false; const suffix = wildcardDomain.slice(1); // e.g. ".wildcard.owen.fosrl.io" return host.endsWith(suffix) && host.length > suffix.length; @@ -228,7 +243,7 @@ export default async function ResourceAuthPage(props: { let loginIdps: LoginFormIDP[] = []; if (build === "saas" || env.app.identityProviderMode === "org") { - if (subscribed) { + if (hasOrgOidc) { const idpsRes = await cache( async () => await priv.get>( @@ -271,7 +286,7 @@ export default async function ResourceAuthPage(props: { let branding: LoadLoginPageBrandingResponse | null = null; try { - if (subscribed) { + if (hasLoginPageBranding) { const res = await priv.get< AxiosResponse >(`/login-page-branding?orgId=${authInfo.orgId}`); diff --git a/src/lib/api/isOrgSubscribed.ts b/src/lib/api/isOrgSubscribed.ts index ce19b9178..16fc082c0 100644 --- a/src/lib/api/isOrgSubscribed.ts +++ b/src/lib/api/isOrgSubscribed.ts @@ -4,9 +4,13 @@ import { getCachedSubscription } from "./getCachedSubscription"; import { priv } from "."; import { AxiosResponse } from "axios"; import { GetLicenseStatusResponse } from "@server/routers/license/types"; +import { Tier } from "@server/types/Tiers"; -export const isOrgSubscribed = cache(async (orgId: string) => { +const DEFAULT_PAID_TIERS: Tier[] = ["tier1", "tier2", "tier3", "enterprise"]; + +export const isOrgSubscribed = cache(async (orgId: string, tiers?: Tier[]) => { let subscribed = false; + const allowedTiers = tiers ?? DEFAULT_PAID_TIERS; if (build === "enterprise") { try { @@ -20,7 +24,8 @@ export const isOrgSubscribed = cache(async (orgId: string) => { try { const subRes = await getCachedSubscription(orgId); subscribed = - (subRes.data.data.tier == "tier1" || subRes.data.data.tier == "tier2" || subRes.data.data.tier == "tier3" || subRes.data.data.tier == "enterprise") && + !!subRes.data.data.tier && + allowedTiers.includes(subRes.data.data.tier as Tier) && subRes.data.data.active; } catch {} } diff --git a/src/lib/loadOrgLoginPageBranding.ts b/src/lib/loadOrgLoginPageBranding.ts index 7e549622a..02ef66295 100644 --- a/src/lib/loadOrgLoginPageBranding.ts +++ b/src/lib/loadOrgLoginPageBranding.ts @@ -1,6 +1,7 @@ import { priv } from "@app/lib/api"; import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed"; import { build } from "@server/build"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { LoadLoginPageBrandingResponse } from "@server/routers/loginPage/types"; import { AxiosResponse } from "axios"; @@ -11,7 +12,10 @@ export async function loadOrgLoginPageBranding(orgId: string): Promise<{ return { primaryColor: null }; } - const subscribed = await isOrgSubscribed(orgId); + const subscribed = await isOrgSubscribed( + orgId, + tierMatrix.loginPageBranding + ); if (!subscribed) { return { primaryColor: null }; } From 5ef068c8dcce00d239b162a3c2da458d7c59af24 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 15:31:37 -0400 Subject: [PATCH 17/94] Set the resource from the site --- server/lib/blueprints/privateResources.ts | 10 +++-- server/lib/blueprints/publicResources.ts | 47 ++++++++++++++++------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index 7d5d9a874..45b76f9f0 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -198,17 +198,19 @@ export async function updatePrivateResources( } } + let resourceStatusFromSite: "approved" | "pending" = "approved"; if (siteId && allSites.length === 0) { // only add if there are not provided sites // Use the provided siteId directly, but verify it belongs to the org const [siteSingle] = await trx - .select({ siteId: sites.siteId }) + .select({ siteId: sites.siteId, status: sites.status }) .from(sites) .where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))) .limit(1); if (siteSingle) { allSites.push(siteSingle); } + resourceStatusFromSite = siteSingle.status ?? "approved"; } if (allSites.length === 0) { @@ -259,7 +261,8 @@ export async function updatePrivateResources( pamMode: resourceData["auth-daemon"]?.pam || "passthrough", authDaemonMode: resourceData["auth-daemon"]?.mode || "native", - authDaemonPort: resourceData["auth-daemon"]?.port || 22123 + authDaemonPort: resourceData["auth-daemon"]?.port || 22123, + status: resourceStatusFromSite }) .where( eq( @@ -512,7 +515,8 @@ export async function updatePrivateResources( pamMode: resourceData["auth-daemon"]?.pam || "passthrough", authDaemonMode: resourceData["auth-daemon"]?.mode || "native", - authDaemonPort: resourceData["auth-daemon"]?.port || 22123 + authDaemonPort: resourceData["auth-daemon"]?.port || 22123, + status: resourceStatusFromSite }) .returning(); diff --git a/server/lib/blueprints/publicResources.ts b/server/lib/blueprints/publicResources.ts index df3b3799e..66ab130a8 100644 --- a/server/lib/blueprints/publicResources.ts +++ b/server/lib/blueprints/publicResources.ts @@ -25,6 +25,7 @@ import { rolePolicies, roleResources, roles, + Site, sites, Target, TargetHealthCheck, @@ -74,19 +75,40 @@ export async function updatePublicResources( )) { const targetsToUpdate: Target[] = []; const healthchecksToUpdate: TargetHealthCheck[] = []; + let resource: Resource; + let resourceStatusFromSite: "approved" | "pending" = "approved"; + let providedSite: Partial | undefined; + if (siteId) { + // Use the provided siteId directly, but verify it belongs to the org + [providedSite] = await trx + .select({ + siteId: sites.siteId, + type: sites.type, + status: sites.status + }) + .from(sites) + .where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))) + .limit(1); + + resourceStatusFromSite = providedSite?.status ?? "approved"; + } async function createTarget( // reusable function to create a target resourceId: number, targetData: TargetData ) { const targetSiteId = targetData.site; - let site; + let site: Partial | undefined; if (targetSiteId) { // Look up site by niceId [site] = await trx - .select({ siteId: sites.siteId, type: sites.type }) + .select({ + siteId: sites.siteId, + type: sites.type, + status: sites.status + }) .from(sites) .where( and( @@ -95,15 +117,9 @@ export async function updatePublicResources( ) ) .limit(1); - } else if (siteId) { + } else if (siteId && providedSite) { // Use the provided siteId directly, but verify it belongs to the org - [site] = await trx - .select({ siteId: sites.siteId, type: sites.type }) - .from(sites) - .where( - and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)) - ) - .limit(1); + site = providedSite; } else { throw new Error(`Target site is required`); } @@ -139,7 +155,7 @@ export async function updatePublicResources( .insert(targets) .values({ resourceId: resourceId, - siteId: site.siteId, + siteId: site.siteId!, ip: targetData.hostname, mode: resourceData.mode as Target["mode"], method: targetData.method, @@ -172,7 +188,7 @@ export async function updatePublicResources( .insert(targetHealthCheck) .values({ name: `${targetData.hostname}:${targetData.port}`, - siteId: site.siteId, + siteId: site.siteId!, targetId: newTarget.targetId, orgId: orgId, hcEnabled: healthcheckData?.enabled || false, @@ -406,7 +422,8 @@ export async function updatePublicResources( ? (resourceData["proxy-protocol-version"] ?? 1) : 1, - resourcePolicyId: sharedPolicy.resourcePolicyId + resourcePolicyId: sharedPolicy.resourcePolicyId, + status: resourceStatusFromSite }) .where( eq( @@ -590,7 +607,8 @@ export async function updatePublicResources( authDaemonPort: resourceData["auth-daemon"]?.port || 22123, resourcePolicyId: null, - defaultResourcePolicyId: inlinePolicyId + defaultResourcePolicyId: inlinePolicyId, + status: resourceStatusFromSite }) .where( eq( @@ -1131,6 +1149,7 @@ export async function updatePublicResources( .values({ orgId, niceId: resourceNiceId, + status: resourceStatusFromSite, name: resourceData.name || "Unnamed Resource", mode: resourceData.mode, proxyPort: ["http", "ssh", "rdp", "vnc"].includes( From e1bc0b7efd26d4d6a7e74aed3051891dc4855bf6 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 15:36:05 -0400 Subject: [PATCH 18/94] Make sure the enabled gets set to false --- server/lib/blueprints/privateResources.ts | 11 +++++++++-- server/lib/blueprints/publicResources.ts | 5 ++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index 45b76f9f0..44901e8c8 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -219,6 +219,13 @@ export async function updatePrivateResources( ); } + const resourceEnabled = + resourceData.enabled == undefined || resourceData.enabled == null + ? true + : resourceStatusFromSite === "pending" + ? false + : resourceData.enabled; + if (existingResource) { let domainInfo: | { subdomain: string | null; domainId: string } @@ -242,7 +249,7 @@ export async function updatePrivateResources( scheme: resourceData.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], - enabled: resourceData.enabled ?? true, + enabled: resourceEnabled, alias: resourceData.alias || null, disableIcmp: resourceData["disable-icmp"] || @@ -495,7 +502,7 @@ export async function updatePrivateResources( scheme: resourceData.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], - enabled: resourceData.enabled ?? true, + enabled: resourceEnabled, alias: resourceData.alias || null, aliasAddress: aliasAddress, disableIcmp: diff --git a/server/lib/blueprints/publicResources.ts b/server/lib/blueprints/publicResources.ts index 66ab130a8..997d56e2a 100644 --- a/server/lib/blueprints/publicResources.ts +++ b/server/lib/blueprints/publicResources.ts @@ -246,7 +246,10 @@ export async function updatePublicResources( const resourceEnabled = resourceData.enabled == undefined || resourceData.enabled == null ? true - : resourceData.enabled; + : resourceStatusFromSite === "pending" + ? false + : resourceData.enabled; + const resourceSsl = resourceData.ssl == undefined || resourceData.ssl == null ? true From d9303f87c800ad519c5868413f2735c30d6e9bdc Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 16:34:02 -0400 Subject: [PATCH 19/94] set last used cookie in smart login lookup --- messages/en-US.json | 2 +- src/components/SmartLoginOrgSelector.tsx | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index ee3e16102..5833647e1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1517,7 +1517,7 @@ "otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.", "otpAuthSubmit": "Submit Code", "idpContinue": "Or continue with", - "idpLastUsed": "Last used", + "idpLastUsed": "Last Used", "otpAuthBack": "Back to Password", "navbar": "Navigation Menu", "navbarDescription": "Main navigation menu for the application", diff --git a/src/components/SmartLoginOrgSelector.tsx b/src/components/SmartLoginOrgSelector.tsx index 79a43782e..22a5bb01c 100644 --- a/src/components/SmartLoginOrgSelector.tsx +++ b/src/components/SmartLoginOrgSelector.tsx @@ -17,6 +17,8 @@ import { } from "next/navigation"; import { cleanRedirect } from "@app/lib/cleanRedirect"; import { Separator } from "@app/components/ui/separator"; +import { setClientCookie } from "@app/lib/setClientCookie"; +import { LAST_USED_IDP_COOKIE_NAME } from "@app/lib/consts"; type SmartLoginOrgSelectorProps = { identifier: string; @@ -141,6 +143,17 @@ export default function SmartLoginOrgSelector({ setPendingIdpId(idpId); setError(null); + setClientCookie( + LAST_USED_IDP_COOKIE_NAME, + JSON.stringify({ + orgId, + idpId + }), + { + sameSite: "Lax" + } + ); + let redirectToUrl: string | undefined; try { const safeRedirect = cleanRedirect(redirect || "/"); From 39c35fa539dc59d969743e278eaa02e146e2c67d Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 17:05:45 -0400 Subject: [PATCH 20/94] reorganize components nad hooks for consistency --- .../resources/private/[niceId]/access/page.tsx | 2 +- .../resources/private/[niceId]/cidr/page.tsx | 12 ++++++------ .../resources/private/[niceId]/general/page.tsx | 2 +- .../resources/private/[niceId]/host/page.tsx | 12 ++++++------ .../resources/private/[niceId]/http/page.tsx | 10 +++++----- .../resources/private/[niceId]/ssh/page.tsx | 10 +++++----- .../settings/resources/private/create/page.tsx | 16 ++++++++++------ .../PrivateResourceAccessFields.tsx | 0 .../PrivateResourceDestinationFields.tsx | 0 .../PrivateResourceHttpFields.tsx | 0 .../PrivateResourceMultiSiteRoutingHelp.tsx | 0 .../PrivateResourcePortRanges.tsx | 0 .../PrivateResourceSitesField.tsx | 2 +- .../PrivateResourceSshFields.tsx | 6 +++--- .../private => hooks}/useSaveSiteResource.ts | 0 .../private => lib}/formControlUtils.ts | 0 .../private => lib}/privateResourceUtils.ts | 0 17 files changed, 38 insertions(+), 34 deletions(-) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceAccessFields.tsx (100%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceDestinationFields.tsx (100%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceHttpFields.tsx (100%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceMultiSiteRoutingHelp.tsx (100%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourcePortRanges.tsx (100%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceSitesField.tsx (98%) rename src/{app/[orgId]/settings/resources/private => components}/PrivateResourceSshFields.tsx (97%) rename src/{app/[orgId]/settings/resources/private => hooks}/useSaveSiteResource.ts (100%) rename src/{app/[orgId]/settings/resources/private => lib}/formControlUtils.ts (100%) rename src/{app/[orgId]/settings/resources/private => lib}/privateResourceUtils.ts (100%) diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/access/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/access/page.tsx index e09366329..70a6504c4 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/access/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/access/page.tsx @@ -29,7 +29,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import { useActionState, useEffect } from "react"; import { useForm } from "react-hook-form"; -import { PrivateResourceAccessFields } from "../../PrivateResourceAccessFields"; +import { PrivateResourceAccessFields } from "@app/components/PrivateResourceAccessFields"; export default function PrivateResourceAccessPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/cidr/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/cidr/page.tsx index 8064ec883..16e603c01 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/cidr/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/cidr/page.tsx @@ -20,12 +20,12 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSitesField } from "../../PrivateResourceSitesField"; -import { PrivateResourceCidrDestinationField } from "../../PrivateResourceDestinationFields"; -import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges"; -import { buildSelectedSitesForResource } from "../../privateResourceUtils"; -import { asAnyControl, asAnySetValue } from "../../formControlUtils"; -import { useSaveSiteResource } from "../../useSaveSiteResource"; +import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; +import { PrivateResourceCidrDestinationField } from "@app/components/PrivateResourceDestinationFields"; +import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges"; +import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; +import { asAnyControl, asAnySetValue } from "@app/lib/formControlUtils"; +import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils"; export default function PrivateResourceCidrPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx index d14b9c3d5..8dbdb3809 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/general/page.tsx @@ -30,7 +30,7 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { useSaveSiteResource } from "../../useSaveSiteResource"; +import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; export default function PrivateResourceGeneralPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/host/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/host/page.tsx index 73d4207cd..bb9734ad0 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/host/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/host/page.tsx @@ -20,16 +20,16 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSitesField } from "../../PrivateResourceSitesField"; -import { PrivateResourceHostDestinationFields } from "../../PrivateResourceDestinationFields"; -import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges"; -import { buildSelectedSitesForResource } from "../../privateResourceUtils"; +import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; +import { PrivateResourceHostDestinationFields } from "@app/components/PrivateResourceDestinationFields"; +import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges"; +import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; import { asAnyControl, asAnySetValue, asAnyWatch -} from "../../formControlUtils"; -import { useSaveSiteResource } from "../../useSaveSiteResource"; +} from "@app/lib/formControlUtils"; +import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils"; export default function PrivateResourceHostPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/http/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/http/page.tsx index 2bbd776e5..c1263c162 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/http/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/http/page.tsx @@ -22,15 +22,15 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSitesField } from "../../PrivateResourceSitesField"; -import { PrivateResourceHttpFields } from "../../PrivateResourceHttpFields"; -import { buildSelectedSitesForResource } from "../../privateResourceUtils"; +import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; +import { PrivateResourceHttpFields } from "@app/components/PrivateResourceHttpFields"; +import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; import { asAnyControl, asAnySetValue, asAnyWatch -} from "../../formControlUtils"; -import { useSaveSiteResource } from "../../useSaveSiteResource"; +} from "@app/lib/formControlUtils"; +import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils"; export default function PrivateResourceHttpPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx index 6de4082b6..9a739a073 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/ssh/page.tsx @@ -26,15 +26,15 @@ import { useTranslations } from "next-intl"; import { useActionState, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSshFields } from "../../PrivateResourceSshFields"; -import { buildSelectedSitesForResource } from "../../privateResourceUtils"; +import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields"; +import type { Selectedsite } from "@app/components/site-selector"; +import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource"; import { asAnyControl, asAnySetValue, asAnyWatch -} from "../../formControlUtils"; -import { useSaveSiteResource } from "../../useSaveSiteResource"; -import type { Selectedsite } from "@app/components/site-selector"; +} from "@app/lib/formControlUtils"; +import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils"; export default function PrivateResourceSshPage() { const t = useTranslations(); diff --git a/src/app/[orgId]/settings/resources/private/create/page.tsx b/src/app/[orgId]/settings/resources/private/create/page.tsx index 49b40e3c9..7e6144172 100644 --- a/src/app/[orgId]/settings/resources/private/create/page.tsx +++ b/src/app/[orgId]/settings/resources/private/create/page.tsx @@ -50,16 +50,20 @@ import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { PrivateResourceSitesField } from "../PrivateResourceSitesField"; -import { PrivateResourceHttpFields } from "../PrivateResourceHttpFields"; -import { PrivateResourceSshFields } from "../PrivateResourceSshFields"; -import { PrivateResourcePortRanges } from "../PrivateResourcePortRanges"; +import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; +import { PrivateResourceHttpFields } from "@app/components/PrivateResourceHttpFields"; +import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields"; +import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges"; import { PrivateResourceAliasField, PrivateResourceCidrDestinationField, PrivateResourceHostDestinationFields -} from "../PrivateResourceDestinationFields"; -import { asAnyControl, asAnySetValue, asAnyWatch } from "../formControlUtils"; +} from "@app/components/PrivateResourceDestinationFields"; +import { + asAnyControl, + asAnySetValue, + asAnyWatch +} from "@app/lib/formControlUtils"; export default function CreatePrivateResourcePage() { const params = useParams(); diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceAccessFields.tsx b/src/components/PrivateResourceAccessFields.tsx similarity index 100% rename from src/app/[orgId]/settings/resources/private/PrivateResourceAccessFields.tsx rename to src/components/PrivateResourceAccessFields.tsx diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceDestinationFields.tsx b/src/components/PrivateResourceDestinationFields.tsx similarity index 100% rename from src/app/[orgId]/settings/resources/private/PrivateResourceDestinationFields.tsx rename to src/components/PrivateResourceDestinationFields.tsx diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceHttpFields.tsx b/src/components/PrivateResourceHttpFields.tsx similarity index 100% rename from src/app/[orgId]/settings/resources/private/PrivateResourceHttpFields.tsx rename to src/components/PrivateResourceHttpFields.tsx diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceMultiSiteRoutingHelp.tsx b/src/components/PrivateResourceMultiSiteRoutingHelp.tsx similarity index 100% rename from src/app/[orgId]/settings/resources/private/PrivateResourceMultiSiteRoutingHelp.tsx rename to src/components/PrivateResourceMultiSiteRoutingHelp.tsx diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourcePortRanges.tsx b/src/components/PrivateResourcePortRanges.tsx similarity index 100% rename from src/app/[orgId]/settings/resources/private/PrivateResourcePortRanges.tsx rename to src/components/PrivateResourcePortRanges.tsx diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceSitesField.tsx b/src/components/PrivateResourceSitesField.tsx similarity index 98% rename from src/app/[orgId]/settings/resources/private/PrivateResourceSitesField.tsx rename to src/components/PrivateResourceSitesField.tsx index 1490db5af..0b0b930cb 100644 --- a/src/app/[orgId]/settings/resources/private/PrivateResourceSitesField.tsx +++ b/src/components/PrivateResourceSitesField.tsx @@ -23,7 +23,7 @@ import { import { ChevronsUpDown } from "lucide-react"; import { useTranslations } from "next-intl"; import type { Control, FieldPath, FieldValues } from "react-hook-form"; -import { PrivateResourceMultiSiteRoutingHelp } from "./PrivateResourceMultiSiteRoutingHelp"; +import { PrivateResourceMultiSiteRoutingHelp } from "@app/components/PrivateResourceMultiSiteRoutingHelp"; type PrivateResourceSitesFieldProps = { control: Control; diff --git a/src/app/[orgId]/settings/resources/private/PrivateResourceSshFields.tsx b/src/components/PrivateResourceSshFields.tsx similarity index 97% rename from src/app/[orgId]/settings/resources/private/PrivateResourceSshFields.tsx rename to src/components/PrivateResourceSshFields.tsx index e61ed2292..edfdde5de 100644 --- a/src/app/[orgId]/settings/resources/private/PrivateResourceSshFields.tsx +++ b/src/components/PrivateResourceSshFields.tsx @@ -9,10 +9,10 @@ import { } from "@app/components/Settings"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { SshServerSettingsFields } from "@app/components/SshServerSettingsFields"; -import { PrivateResourceAliasField } from "./PrivateResourceDestinationFields"; -import { PrivateResourceSitesField } from "./PrivateResourceSitesField"; -import { getSshUseMultiSiteTargetForm } from "./privateResourceUtils"; +import { PrivateResourceAliasField } from "@app/components/PrivateResourceDestinationFields"; +import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField"; import { inferSshPamMode } from "@app/lib/privateResourceForm"; +import { getSshUseMultiSiteTargetForm } from "@app/lib/privateResourceUtils"; import { FormControl, FormField, diff --git a/src/app/[orgId]/settings/resources/private/useSaveSiteResource.ts b/src/hooks/useSaveSiteResource.ts similarity index 100% rename from src/app/[orgId]/settings/resources/private/useSaveSiteResource.ts rename to src/hooks/useSaveSiteResource.ts diff --git a/src/app/[orgId]/settings/resources/private/formControlUtils.ts b/src/lib/formControlUtils.ts similarity index 100% rename from src/app/[orgId]/settings/resources/private/formControlUtils.ts rename to src/lib/formControlUtils.ts diff --git a/src/app/[orgId]/settings/resources/private/privateResourceUtils.ts b/src/lib/privateResourceUtils.ts similarity index 100% rename from src/app/[orgId]/settings/resources/private/privateResourceUtils.ts rename to src/lib/privateResourceUtils.ts From 7d475f5e9186886c453975a6325aede1d9fe90e9 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 17:18:33 -0400 Subject: [PATCH 21/94] filter out pending resources --- src/app/[orgId]/settings/resources/private/page.tsx | 1 + src/app/[orgId]/settings/resources/public/page.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/[orgId]/settings/resources/private/page.tsx b/src/app/[orgId]/settings/resources/private/page.tsx index 8206d14e8..348340aa9 100644 --- a/src/app/[orgId]/settings/resources/private/page.tsx +++ b/src/app/[orgId]/settings/resources/private/page.tsx @@ -27,6 +27,7 @@ export default async function ClientResourcesPage( const params = await props.params; const t = await getTranslations(); const searchParams = new URLSearchParams(await props.searchParams); + searchParams.set("status", "approved"); let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = []; let pagination: ListAllSiteResourcesByOrgResponse["pagination"] = { diff --git a/src/app/[orgId]/settings/resources/public/page.tsx b/src/app/[orgId]/settings/resources/public/page.tsx index e0b9f6210..f8cc85dae 100644 --- a/src/app/[orgId]/settings/resources/public/page.tsx +++ b/src/app/[orgId]/settings/resources/public/page.tsx @@ -38,6 +38,7 @@ export default async function ProxyResourcesPage( const params = await props.params; const t = await getTranslations(); const searchParams = new URLSearchParams(await props.searchParams); + searchParams.set("status", "approved"); let resources: ListResourcesResponse["resources"] = []; let pagination: ListResourcesResponse["pagination"] = { From 34b18bdb53396807e0544dcb56249da75eef6cff Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 17:24:10 -0400 Subject: [PATCH 22/94] only show approved resources in launcher --- .../launcher/launcherResourceAccess.ts | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/server/routers/launcher/launcherResourceAccess.ts b/server/routers/launcher/launcherResourceAccess.ts index ec4500262..ce94a7a50 100644 --- a/server/routers/launcher/launcherResourceAccess.ts +++ b/server/routers/launcher/launcherResourceAccess.ts @@ -157,7 +157,8 @@ async function resolveAccessibleIdsUncached( .where( and( eq(userResources.userId, userId), - eq(resources.orgId, orgId) + eq(resources.orgId, orgId), + eq(resources.status, "approved") ) ), userRoleIds.length > 0 @@ -171,7 +172,8 @@ async function resolveAccessibleIdsUncached( .where( and( inArray(roleResources.roleId, userRoleIds), - eq(resources.orgId, orgId) + eq(resources.orgId, orgId), + eq(resources.status, "approved") ) ) : Promise.resolve([]), @@ -183,7 +185,11 @@ async function resolveAccessibleIdsUncached( eq(effectiveResourcePolicyId, userPolicies.resourcePolicyId) ) .where( - and(eq(userPolicies.userId, userId), eq(resources.orgId, orgId)) + and( + eq(userPolicies.userId, userId), + eq(resources.orgId, orgId), + eq(resources.status, "approved") + ) ), userRoleIds.length > 0 ? db @@ -199,21 +205,48 @@ async function resolveAccessibleIdsUncached( .where( and( inArray(rolePolicies.roleId, userRoleIds), - eq(resources.orgId, orgId) + eq(resources.orgId, orgId), + eq(resources.status, "approved") ) ) : Promise.resolve([]), db .select({ siteResourceId: userSiteResources.siteResourceId }) .from(userSiteResources) - .where(eq(userSiteResources.userId, userId)), + .innerJoin( + siteResources, + eq( + userSiteResources.siteResourceId, + siteResources.siteResourceId + ) + ) + .where( + and( + eq(userSiteResources.userId, userId), + eq(siteResources.orgId, orgId), + eq(siteResources.status, "approved") + ) + ), userRoleIds.length > 0 ? db .select({ siteResourceId: roleSiteResources.siteResourceId }) .from(roleSiteResources) - .where(inArray(roleSiteResources.roleId, userRoleIds)) + .innerJoin( + siteResources, + eq( + roleSiteResources.siteResourceId, + siteResources.siteResourceId + ) + ) + .where( + and( + inArray(roleSiteResources.roleId, userRoleIds), + eq(siteResources.orgId, orgId), + eq(siteResources.status, "approved") + ) + ) : Promise.resolve([]) ]); @@ -365,6 +398,7 @@ async function filterPublicResourceIdsByTextSearch( inArray(resources.resourceId, resourceIds), eq(resources.orgId, orgId), eq(resources.enabled, true), + eq(resources.status, "approved"), textMatch ) ); @@ -402,6 +436,7 @@ async function filterSiteResourceIdsByTextSearch( inArray(siteResources.siteResourceId, siteResourceIds), eq(siteResources.orgId, orgId), eq(siteResources.enabled, true), + eq(siteResources.status, "approved"), textMatch ) ); @@ -503,7 +538,8 @@ async function listSiteGroups( const publicConditions = [ inArray(resources.resourceId, accessible.resourceIds), eq(resources.orgId, orgId), - eq(resources.enabled, true) + eq(resources.enabled, true), + eq(resources.status, "approved") ]; if (searchPublic) { publicConditions.push(searchPublic); @@ -558,7 +594,8 @@ async function listSiteGroups( const siteConditions = [ inArray(siteResources.siteResourceId, accessible.siteResourceIds), eq(siteResources.orgId, orgId), - eq(siteResources.enabled, true) + eq(siteResources.enabled, true), + eq(siteResources.status, "approved") ]; if (searchSite) { siteConditions.push(searchSite); @@ -621,7 +658,8 @@ async function listSiteGroups( const noSitePublicConditions = [ inArray(resources.resourceId, accessible.resourceIds), eq(resources.orgId, orgId), - eq(resources.enabled, true) + eq(resources.enabled, true), + eq(resources.status, "approved") ]; if (searchPublic) { noSitePublicConditions.push(searchPublic); @@ -655,7 +693,8 @@ async function listSiteGroups( const noSiteSiteConditions = [ inArray(siteResources.siteResourceId, accessible.siteResourceIds), eq(siteResources.orgId, orgId), - eq(siteResources.enabled, true) + eq(siteResources.enabled, true), + eq(siteResources.status, "approved") ]; if (searchSite) { noSiteSiteConditions.push(searchSite); @@ -746,7 +785,8 @@ async function listLabelGroups( const publicConditions = [ inArray(resources.resourceId, accessible.resourceIds), eq(resources.orgId, orgId), - eq(resources.enabled, true) + eq(resources.enabled, true), + eq(resources.status, "approved") ]; const searchPublic = buildSearchConditionForPublic(query.query); if (searchPublic) { @@ -810,7 +850,8 @@ async function listLabelGroups( const siteConditions = [ inArray(siteResources.siteResourceId, accessible.siteResourceIds), eq(siteResources.orgId, orgId), - eq(siteResources.enabled, true) + eq(siteResources.enabled, true), + eq(siteResources.status, "approved") ]; const searchSite = buildSearchConditionForSiteResource(query.query); if (searchSite) { @@ -997,6 +1038,7 @@ async function mapPublicResources( inArray(resources.resourceId, resourceIds), eq(resources.orgId, orgId), eq(resources.enabled, true), + eq(resources.status, "approved"), siteIdFilter != null ? eq(sites.siteId, siteIdFilter) : undefined @@ -1088,6 +1130,7 @@ async function mapSiteResources( inArray(siteResources.siteResourceId, siteResourceIds), eq(siteResources.orgId, orgId), eq(siteResources.enabled, true), + eq(siteResources.status, "approved"), siteIdFilter != null ? eq(sites.siteId, siteIdFilter) : undefined @@ -1382,7 +1425,8 @@ async function collectAccessibleSites( const publicConditions = [ inArray(resources.resourceId, accessible.resourceIds), eq(resources.orgId, orgId), - eq(resources.enabled, true) + eq(resources.enabled, true), + eq(resources.status, "approved") ]; if (siteNameSearch) { publicConditions.push(siteNameSearch); @@ -1422,7 +1466,8 @@ async function collectAccessibleSites( const siteConditions = [ inArray(siteResources.siteResourceId, accessible.siteResourceIds), eq(siteResources.orgId, orgId), - eq(siteResources.enabled, true) + eq(siteResources.enabled, true), + eq(siteResources.status, "approved") ]; if (siteNameSearch) { siteConditions.push(siteNameSearch); @@ -1476,6 +1521,7 @@ async function collectAccessibleLabels( inArray(resources.resourceId, accessible.resourceIds), eq(resources.orgId, orgId), eq(resources.enabled, true), + eq(resources.status, "approved"), eq(labels.orgId, orgId) ]; if (labelNameSearch) { @@ -1511,6 +1557,7 @@ async function collectAccessibleLabels( inArray(siteResources.siteResourceId, accessible.siteResourceIds), eq(siteResources.orgId, orgId), eq(siteResources.enabled, true), + eq(siteResources.status, "approved"), eq(labels.orgId, orgId) ]; if (labelNameSearch) { From 591cb9cdc1eaf27dcaed424904f8204bcf058318 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 10 Jul 2026 18:05:16 -0400 Subject: [PATCH 23/94] dont use strict query params in list alises --- server/routers/resource/listUserResourceAliases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/resource/listUserResourceAliases.ts b/server/routers/resource/listUserResourceAliases.ts index 100b1624d..98e73ad85 100644 --- a/server/routers/resource/listUserResourceAliases.ts +++ b/server/routers/resource/listUserResourceAliases.ts @@ -57,7 +57,7 @@ const listUserResourceAliasesParamsSchema = z.strictObject({ orgId: z.string() }); -const listUserResourceAliasesQuerySchema = z.strictObject({ +const listUserResourceAliasesQuerySchema = z.object({ pageSize: z.coerce .number() .int() From 933ca71c16fa5c94856cf586c1b7aa38b3a1481b Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sun, 12 Jul 2026 17:16:08 -0400 Subject: [PATCH 24/94] add approve site endpoint --- server/routers/external.ts | 8 + server/routers/integration.ts | 8 + server/routers/site/approveSite.ts | 251 +++++++++++++++++++++++++++ server/routers/site/index.ts | 1 + server/routers/site/updateSite.ts | 1 - src/components/PendingSitesTable.tsx | 2 +- 6 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 server/routers/site/approveSite.ts diff --git a/server/routers/external.ts b/server/routers/external.ts index 9e4c5b3f1..9b9c48293 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -248,6 +248,14 @@ authenticated.post( site.updateSite ); +authenticated.post( + "/site/:siteId/approve", + verifySiteAccess, + verifyUserHasAction(ActionsEnum.updateSite), + logActionAudit(ActionsEnum.updateSite), + site.approveSite +); + authenticated.delete( "/site/:siteId", verifySiteAccess, diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 74ea20078..f6ab6bf6c 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -138,6 +138,14 @@ authenticated.post( logActionAudit(ActionsEnum.updateSite), site.updateSite ); + +authenticated.post( + "/site/:siteId/approve", + verifyApiKeySiteAccess, + verifyApiKeyHasAction(ActionsEnum.updateSite), + logActionAudit(ActionsEnum.updateSite), + site.approveSite +); authenticated.post( "/org/:orgId/reset-bandwidth", verifyApiKeyOrgAccess, diff --git a/server/routers/site/approveSite.ts b/server/routers/site/approveSite.ts new file mode 100644 index 000000000..19e2795ad --- /dev/null +++ b/server/routers/site/approveSite.ts @@ -0,0 +1,251 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { + db, + resources, + siteNetworks, + siteResources, + sites, + type Site, + type SiteResource +} from "@server/db"; +import { and, eq, inArray } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; +import { + getResourceIdsForSite, + getSiteResourceIdsForSite +} from "@server/lib/deleteSiteAssociatedResources"; +import { + handleMessagingForUpdatedSiteResource, + rebuildClientAssociationsFromSiteResource, + waitForSiteResourceRebuildIdle +} from "@server/lib/rebuildClientAssociations"; + +const approveSiteParamsSchema = z.strictObject({ + siteId: z.coerce.number().int().positive() +}); + +registry.registerPath({ + method: "post", + path: "/site/{siteId}/approve", + description: + "Approve a pending site and approve (and enable when needed) its associated resources.", + tags: [OpenAPITags.Site], + request: { + params: approveSiteParamsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + +type SiteResourceEnableSideEffect = { + existing: SiteResource; + updated: SiteResource; + siteIds: number[]; +}; + +export async function approveSite( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = approveSiteParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { siteId } = parsedParams.data; + + const [existingSite] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!existingSite) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found` + ) + ); + } + + if (!existingSite.orgId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Site with ID ${siteId} has no organization` + ) + ); + } + + const orgId = existingSite.orgId; + let updatedSite: Site | undefined; + const siteResourceEnableSideEffects: SiteResourceEnableSideEffect[] = + []; + + await db.transaction(async (trx) => { + [updatedSite] = await trx + .update(sites) + .set({ status: "approved" }) + .where(eq(sites.siteId, siteId)) + .returning(); + + const resourceIds = await getResourceIdsForSite(siteId, trx); + const siteResourceIds = await getSiteResourceIdsForSite( + siteId, + orgId, + trx + ); + + if (resourceIds.length > 0) { + const pendingDisabledResources = await trx + .select({ resourceId: resources.resourceId }) + .from(resources) + .where( + and( + inArray(resources.resourceId, resourceIds), + eq(resources.status, "pending"), + eq(resources.enabled, false) + ) + ); + + await trx + .update(resources) + .set({ status: "approved" }) + .where(inArray(resources.resourceId, resourceIds)); + + if (pendingDisabledResources.length > 0) { + await trx + .update(resources) + .set({ enabled: true }) + .where( + inArray( + resources.resourceId, + pendingDisabledResources.map( + (r) => r.resourceId + ) + ) + ); + } + } + + if (siteResourceIds.length > 0) { + const existingSiteResources = await trx + .select() + .from(siteResources) + .where( + inArray(siteResources.siteResourceId, siteResourceIds) + ); + + const pendingDisabledSiteResources = + existingSiteResources.filter( + (sr) => sr.status === "pending" && !sr.enabled + ); + + await trx + .update(siteResources) + .set({ status: "approved" }) + .where( + inArray(siteResources.siteResourceId, siteResourceIds) + ); + + if (pendingDisabledSiteResources.length > 0) { + const enableIds = pendingDisabledSiteResources.map( + (sr) => sr.siteResourceId + ); + + const updatedEnabledSiteResources = await trx + .update(siteResources) + .set({ enabled: true }) + .where(inArray(siteResources.siteResourceId, enableIds)) + .returning(); + + for (const updated of updatedEnabledSiteResources) { + const existing = pendingDisabledSiteResources.find( + (sr) => sr.siteResourceId === updated.siteResourceId + ); + if (!existing || !updated.networkId) { + continue; + } + + const networkSites = await trx + .select({ siteId: siteNetworks.siteId }) + .from(siteNetworks) + .where( + eq(siteNetworks.networkId, updated.networkId) + ); + + siteResourceEnableSideEffects.push({ + existing, + updated, + siteIds: networkSites.map((s) => s.siteId) + }); + } + } + } + }); + + for (const sideEffect of siteResourceEnableSideEffects) { + rebuildClientAssociationsFromSiteResource(sideEffect.updated) + .then(() => + waitForSiteResourceRebuildIdle( + sideEffect.updated.siteResourceId + ) + ) + .then(() => + handleMessagingForUpdatedSiteResource( + sideEffect.existing, + sideEffect.updated, + sideEffect.siteIds, + sideEffect.siteIds + ) + ) + .catch((e) => { + logger.error( + `Failed to rebuild and handle messaging for site resource ${sideEffect.updated.siteResourceId} after site approval:`, + e + ); + }); + } + + return response(res, { + data: updatedSite ?? null, + success: true, + error: false, + message: "Site approved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index 292c57971..43b778f62 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -3,6 +3,7 @@ export * from "./getStatusHistory"; export * from "./createSite"; export * from "./deleteSite"; export * from "./updateSite"; +export * from "./approveSite"; export * from "./listSites"; export * from "./listSiteRoles"; export * from "./pickSiteDefaults"; diff --git a/server/routers/site/updateSite.ts b/server/routers/site/updateSite.ts index c6851a3c3..42ab8b246 100644 --- a/server/routers/site/updateSite.ts +++ b/server/routers/site/updateSite.ts @@ -21,7 +21,6 @@ const updateSiteBodySchema = z name: z.string().min(1).max(255).optional(), niceId: z.string().min(1).max(255).optional(), dockerSocketEnabled: z.boolean().optional(), - status: z.enum(["pending", "approved"]).optional(), autoUpdateEnabled: z.boolean().optional(), autoUpdateOverrideOrg: z.boolean().optional() }) diff --git a/src/components/PendingSitesTable.tsx b/src/components/PendingSitesTable.tsx index 8cdfc424d..e2af5ee4e 100644 --- a/src/components/PendingSitesTable.tsx +++ b/src/components/PendingSitesTable.tsx @@ -111,7 +111,7 @@ export default function PendingSitesTable({ async function approveSite(siteId: number) { setApprovingIds((prev) => new Set(prev).add(siteId)); try { - await api.post(`/site/${siteId}`, { status: "approved" }); + await api.post(`/site/${siteId}/approve`); toast({ title: t("success"), description: t("siteApproveSuccess"), From a8f3f710213ea16400cafd4d46410d2719b1d420 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sun, 12 Jul 2026 17:48:48 -0400 Subject: [PATCH 25/94] add reject endpoint --- messages/en-US.json | 10 +- server/auth/actions.ts | 1 + server/lib/deleteResource.ts | 8 +- server/lib/deleteSiteAssociatedResources.ts | 85 +++++++++- server/routers/external.ts | 12 +- server/routers/integration.ts | 7 - server/routers/site/deleteSite.ts | 18 +- server/routers/site/index.ts | 1 + server/routers/site/rejectSite.ts | 178 ++++++++++++++++++++ src/components/PendingSitesTable.tsx | 26 +-- src/components/PermissionsSelectBox.tsx | 4 +- 11 files changed, 309 insertions(+), 41 deletions(-) create mode 100644 server/routers/site/rejectSite.ts diff --git a/messages/en-US.json b/messages/en-US.json index 2a2c83b18..549865dcf 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -449,8 +449,14 @@ "provisioningManage": "Provisioning", "provisioningDescription": "Manage provisioning keys and review pending sites awaiting approval.", "pendingSites": "Pending Sites", - "siteApproveSuccess": "Site approved successfully", + "siteApproveSuccess": "Site and associated resources approved successfully", "siteApproveError": "Error approving site", + "siteReject": "Reject Site", + "siteQuestionReject": "Are you sure you want to reject this site?", + "siteMessageReject": "This will permanently delete the site and any associated resources that are still pending.", + "siteConfirmReject": "Confirm Reject Site", + "siteRejectSuccess": "Site rejected successfully", + "siteRejectError": "Error rejecting site", "provisioningKeys": "Provisioning Keys", "searchProvisioningKeys": "Search provisioning keys...", "provisioningKeysAdd": "Generate Provisioning Key", @@ -1420,6 +1426,8 @@ "setupTokenDescription": "Enter the setup token from the server console.", "setupTokenRequired": "Setup token is required", "actionUpdateSite": "Update Site", + "actionApproveSite": "Approve Site", + "actionRejectSite": "Reject Site", "actionResetSiteBandwidth": "Reset Organization Bandwidth", "actionListSiteRoles": "List Allowed Site Roles", "actionCreateResource": "Create Resource", diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 50c98c528..741d7a057 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -21,6 +21,7 @@ export enum ActionsEnum { getSite = "getSite", listSites = "listSites", updateSite = "updateSite", + updateSiteApprovals = "updateSiteApprovals", restartSite = "restartSite", resetSiteBandwidth = "resetSiteBandwidth", reGenerateSecret = "reGenerateSecret", diff --git a/server/lib/deleteResource.ts b/server/lib/deleteResource.ts index b2ffa0f0f..3f33c7400 100644 --- a/server/lib/deleteResource.ts +++ b/server/lib/deleteResource.ts @@ -14,8 +14,6 @@ import { } from "@server/db"; import logger from "@server/logger"; import { removeTargets } from "@server/routers/newt/targets"; -import createHttpError from "http-errors"; -import HttpCode from "@server/types/HttpCode"; export type DeleteResourceResult = { deletedResource: Resource; @@ -117,10 +115,10 @@ export async function runResourceDeleteSideEffects( .limit(1); if (!site) { - throw createHttpError( - HttpCode.NOT_FOUND, - `Site with ID ${target.siteId} not found` + logger.debug( + `Site with ID ${target.siteId} not found during resource delete side effects; skipping target removal` ); + continue; } if (site.pubKey && site.type === "newt") { diff --git a/server/lib/deleteSiteAssociatedResources.ts b/server/lib/deleteSiteAssociatedResources.ts index 61da82f58..e69585d1a 100644 --- a/server/lib/deleteSiteAssociatedResources.ts +++ b/server/lib/deleteSiteAssociatedResources.ts @@ -1,6 +1,7 @@ -import { and, eq, sql } from "drizzle-orm"; +import { and, eq, inArray, sql } from "drizzle-orm"; import { db, + resources, siteNetworks, siteResources, targets, @@ -97,6 +98,64 @@ export function exceedsSiteAssociatedResourceDeleteLimit( return resourceCount > MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE; } +export async function getPendingResourceIdsForSite( + siteId: number, + trx: Transaction | typeof db = db +): Promise { + const resourceIds = await getResourceIdsForSite(siteId, trx); + if (resourceIds.length === 0) { + return []; + } + + const rows = await trx + .select({ resourceId: resources.resourceId }) + .from(resources) + .where( + and( + inArray(resources.resourceId, resourceIds), + eq(resources.status, "pending") + ) + ); + + return rows.map((row) => row.resourceId); +} + +export async function getPendingSiteResourceIdsForSite( + siteId: number, + orgId: string, + trx: Transaction | typeof db = db +): Promise { + const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx); + if (siteResourceIds.length === 0) { + return []; + } + + const rows = await trx + .select({ siteResourceId: siteResources.siteResourceId }) + .from(siteResources) + .where( + and( + inArray(siteResources.siteResourceId, siteResourceIds), + eq(siteResources.status, "pending") + ) + ); + + return rows.map((row) => row.siteResourceId); +} + +export async function getPendingAssociatedResourceCountForSite( + siteId: number, + orgId: string, + trx: Transaction | typeof db = db +): Promise { + const [resourceIds, siteResourceIds] = await Promise.all([ + getPendingResourceIdsForSite(siteId, trx), + getPendingSiteResourceIdsForSite(siteId, orgId, trx) + ]); + + return resourceIds.length + siteResourceIds.length; +} + export async function deleteAssociatedResourcesForSite( siteId: number, orgId: string, @@ -105,12 +164,32 @@ export async function deleteAssociatedResourcesForSite( const resourceIds = await getResourceIdsForSite(siteId, trx); const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx); - const [resources, siteResourcesDeleted] = await Promise.all([ + const [deletedResources, siteResourcesDeleted] = await Promise.all([ performDeleteResources(resourceIds, trx), performDeleteSiteResources(siteResourceIds, trx) ]); - return { resources, siteResources: siteResourcesDeleted }; + return { resources: deletedResources, siteResources: siteResourcesDeleted }; +} + +export async function deletePendingAssociatedResourcesForSite( + siteId: number, + orgId: string, + trx: Transaction | typeof db = db +): Promise { + const resourceIds = await getPendingResourceIdsForSite(siteId, trx); + const siteResourceIds = await getPendingSiteResourceIdsForSite( + siteId, + orgId, + trx + ); + + const [deletedResources, siteResourcesDeleted] = await Promise.all([ + performDeleteResources(resourceIds, trx), + performDeleteSiteResources(siteResourceIds, trx) + ]); + + return { resources: deletedResources, siteResources: siteResourcesDeleted }; } export async function runDeleteSiteAssociatedResourcesSideEffects( diff --git a/server/routers/external.ts b/server/routers/external.ts index 9b9c48293..62ca15316 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -251,11 +251,19 @@ authenticated.post( authenticated.post( "/site/:siteId/approve", verifySiteAccess, - verifyUserHasAction(ActionsEnum.updateSite), - logActionAudit(ActionsEnum.updateSite), + verifyUserHasAction(ActionsEnum.updateSiteApprovals), + logActionAudit(ActionsEnum.updateSiteApprovals), site.approveSite ); +authenticated.post( + "/site/:siteId/reject", + verifySiteAccess, + verifyUserHasAction(ActionsEnum.updateSiteApprovals), + logActionAudit(ActionsEnum.updateSiteApprovals), + site.rejectSite +); + authenticated.delete( "/site/:siteId", verifySiteAccess, diff --git a/server/routers/integration.ts b/server/routers/integration.ts index f6ab6bf6c..3abd664eb 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -139,13 +139,6 @@ authenticated.post( site.updateSite ); -authenticated.post( - "/site/:siteId/approve", - verifyApiKeySiteAccess, - verifyApiKeyHasAction(ActionsEnum.updateSite), - logActionAudit(ActionsEnum.updateSite), - site.approveSite -); authenticated.post( "/org/:orgId/reset-bandwidth", verifyApiKeyOrgAccess, diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 602032ffb..2126b69d8 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -159,15 +159,21 @@ export async function deleteSite( siteResources: [] }; - await db.transaction(async (trx) => { - if (deleteResources) { + if (deleteResources) { + await db.transaction(async (trx) => { resourceSideEffects = await deleteAssociatedResourcesForSite( siteId, site.orgId, trx ); - } + }); + await runDeleteSiteAssociatedResourcesSideEffects( + resourceSideEffects + ); + } + + await db.transaction(async (trx) => { if (site.type == "wireguard") { if (site.pubKey) { await deletePeer(site.exitNodeId!, site.pubKey); @@ -180,12 +186,6 @@ export async function deleteSite( await usageService.add(site.orgId, LimitId.SITES, -1, trx); }); - if (deleteResources) { - await runDeleteSiteAssociatedResourcesSideEffects( - resourceSideEffects - ); - } - if (deletedNewt) { const payload = { type: `newt/wg/terminate`, diff --git a/server/routers/site/index.ts b/server/routers/site/index.ts index 43b778f62..c0c399789 100644 --- a/server/routers/site/index.ts +++ b/server/routers/site/index.ts @@ -4,6 +4,7 @@ export * from "./createSite"; export * from "./deleteSite"; export * from "./updateSite"; export * from "./approveSite"; +export * from "./rejectSite"; export * from "./listSites"; export * from "./listSiteRoles"; export * from "./pickSiteDefaults"; diff --git a/server/routers/site/rejectSite.ts b/server/routers/site/rejectSite.ts new file mode 100644 index 000000000..037879e82 --- /dev/null +++ b/server/routers/site/rejectSite.ts @@ -0,0 +1,178 @@ +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { db } from "@server/db"; +import { newts, sites } from "@server/db"; +import { eq } from "drizzle-orm"; +import response from "@server/lib/response"; +import HttpCode from "@server/types/HttpCode"; +import createHttpError from "http-errors"; +import logger from "@server/logger"; +import { deletePeer } from "../gerbil/peers"; +import { fromError } from "zod-validation-error"; +import { sendToClient } from "#dynamic/routers/ws"; +import { OpenAPITags, registry } from "@server/openApi"; +import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations"; +import { usageService } from "@server/lib/billing/usageService"; +import { LimitId } from "@server/lib/billing"; +import { + deletePendingAssociatedResourcesForSite, + exceedsSiteAssociatedResourceDeleteLimit, + getPendingAssociatedResourceCountForSite, + runDeleteSiteAssociatedResourcesSideEffects, + MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE, + type DeleteSiteAssociatedResourcesSideEffects +} from "@server/lib/deleteSiteAssociatedResources"; + +const rejectSiteParamsSchema = z.strictObject({ + siteId: z.coerce.number().int().positive() +}); + +registry.registerPath({ + method: "post", + path: "/site/{siteId}/reject", + description: + "Reject a pending site by deleting it and any associated resources that are still pending.", + tags: [OpenAPITags.Site], + request: { + params: rejectSiteParamsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + +export async function rejectSite( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = rejectSiteParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { siteId } = parsedParams.data; + + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!site) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found` + ) + ); + } + + if (!site.orgId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Site with ID ${siteId} has no organization` + ) + ); + } + + const pendingAssociatedResourceCount = + await getPendingAssociatedResourceCountForSite(siteId, site.orgId); + + if ( + exceedsSiteAssociatedResourceDeleteLimit( + pendingAssociatedResourceCount + ) + ) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + `Cannot reject site and associated pending resources when the site has more than ${MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE} pending resources` + ) + ); + } + + const [deletedNewt] = await db + .select() + .from(newts) + .where(eq(newts.siteId, siteId)) + .limit(1); + + let resourceSideEffects: DeleteSiteAssociatedResourcesSideEffects = { + resources: [], + siteResources: [] + }; + + await db.transaction(async (trx) => { + resourceSideEffects = await deletePendingAssociatedResourcesForSite( + siteId, + site.orgId, + trx + ); + }); + + await runDeleteSiteAssociatedResourcesSideEffects(resourceSideEffects); + + await db.transaction(async (trx) => { + if (site.type == "wireguard") { + if (site.pubKey) { + await deletePeer(site.exitNodeId!, site.pubKey); + } + } else if (site.type == "newt") { + await cleanupSiteAssociations(site, trx); + } + + await trx.delete(sites).where(eq(sites.siteId, siteId)); + await usageService.add(site.orgId, LimitId.SITES, -1, trx); + }); + + if (deletedNewt) { + const payload = { + type: `newt/wg/terminate`, + data: {} + }; + sendToClient(deletedNewt.newtId, payload).catch((error) => { + logger.error( + "Failed to send termination message to newt:", + error + ); + }); + } + + return response(res, { + data: null, + success: true, + error: false, + message: "Site rejected successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + if (createHttpError.isHttpError(error)) { + return next(error); + } + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/src/components/PendingSitesTable.tsx b/src/components/PendingSitesTable.tsx index e2af5ee4e..5981bf387 100644 --- a/src/components/PendingSitesTable.tsx +++ b/src/components/PendingSitesTable.tsx @@ -65,7 +65,7 @@ export default function PendingSitesTable({ const [isRefreshing, startTransition] = useTransition(); const [approvingIds, setApprovingIds] = useState>(new Set()); const [rejectingIds, setRejectingIds] = useState>(new Set()); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isRejectModalOpen, setIsRejectModalOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); const api = createApiClient(useEnvContext()); @@ -136,20 +136,20 @@ export default function PendingSitesTable({ async function rejectSite(siteId: number) { setRejectingIds((prev) => new Set(prev).add(siteId)); try { - await api.delete(`/site/${siteId}`); + await api.post(`/site/${siteId}/reject`); toast({ title: t("success"), - description: t("siteDeleted"), + description: t("siteRejectSuccess"), variant: "default" }); - setIsDeleteModalOpen(false); + setIsRejectModalOpen(false); setSelectedSite(null); router.refresh(); } catch (e) { toast({ variant: "destructive", - title: t("siteErrorDelete"), - description: formatAxiosError(e, t("siteErrorDelete")) + title: t("siteRejectError"), + description: formatAxiosError(e, t("siteRejectError")) }); } finally { setRejectingIds((prev) => { @@ -445,7 +445,7 @@ export default function PendingSitesTable({ disabled={isApproving || isRejecting} onClick={() => { setSelectedSite(siteRow); - setIsDeleteModalOpen(true); + setIsRejectModalOpen(true); }} > @@ -493,23 +493,23 @@ export default function PendingSitesTable({ <> {selectedSite && ( { - setIsDeleteModalOpen(val); + setIsRejectModalOpen(val); if (!val) { setSelectedSite(null); } }} dialog={
-

{t("siteQuestionRemove")}

-

{t("siteMessageRemove")}

+

{t("siteQuestionReject")}

+

{t("siteMessageReject")}

} - buttonText={t("siteConfirmDelete")} + buttonText={t("siteConfirmReject")} onConfirm={async () => rejectSite(selectedSite.id)} string={selectedSite.name} - title={t("siteDelete")} + title={t("siteReject")} /> )} Date: Sun, 12 Jul 2026 17:53:57 -0400 Subject: [PATCH 26/94] add resources overview column to pending sites table --- src/components/PendingSitesTable.tsx | 74 ++++++++++++++++++++++++ src/components/SiteResourcesOverview.tsx | 35 ++++++----- 2 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/components/PendingSitesTable.tsx b/src/components/PendingSitesTable.tsx index 5981bf387..9e0fa0ad8 100644 --- a/src/components/PendingSitesTable.tsx +++ b/src/components/PendingSitesTable.tsx @@ -1,6 +1,16 @@ "use client"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; +import { + Credenza, + CredenzaBody, + CredenzaContent, + CredenzaDescription, + CredenzaFooter, + CredenzaHeader, + CredenzaTitle +} from "@app/components/Credenza"; +import SiteResourcesOverview from "@app/components/SiteResourcesOverview"; import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; import { @@ -24,6 +34,7 @@ import { ArrowUp10Icon, ArrowUpRight, Check, + ChevronDown, ChevronsUpDownIcon, MoreHorizontal, X @@ -67,6 +78,8 @@ export default function PendingSitesTable({ const [rejectingIds, setRejectingIds] = useState>(new Set()); const [isRejectModalOpen, setIsRejectModalOpen] = useState(false); const [selectedSite, setSelectedSite] = useState(null); + const [resourcesDialogSite, setResourcesDialogSite] = + useState(null); const api = createApiClient(useEnvContext()); const t = useTranslations(); @@ -342,6 +355,29 @@ export default function PendingSitesTable({ } } }, + { + id: "resources", + accessorKey: "resourceCount", + friendlyName: t("resources"), + header: () => {t("resources")}, + cell: ({ row }) => { + const siteRow = row.original; + return ( + + ); + } + }, { accessorKey: "exitNode", friendlyName: t("exitNode"), @@ -491,6 +527,44 @@ export default function PendingSitesTable({ return ( <> + { + if (!open) setResourcesDialogSite(null); + }} + > + + + {t("siteResourcesTab")} + + {t("siteResourcesDialogDescription")} + + + + {resourcesDialogSite != null && ( + + )} + + + + + + + {selectedSite && ( - - {viewAllLabel} - + {viewAllHref && viewAllLabel ? ( + + {viewAllLabel} + + ) : null} ); @@ -319,6 +321,8 @@ type SiteResourcesOverviewProps = { initialPrivateForbidden: boolean; /** When not under `/[orgId]/...` routes, pass org id explicitly (e.g. credenza on sites list). */ orgIdOverride?: string; + /** When false, hides links to the org resources tables filtered by this site. */ + showViewAllLinks?: boolean; }; export default function SiteResourcesOverview({ @@ -327,7 +331,8 @@ export default function SiteResourcesOverview({ initialPrivateData, initialPublicForbidden, initialPrivateForbidden, - orgIdOverride + orgIdOverride, + showViewAllLinks = true }: SiteResourcesOverviewProps) { const t = useTranslations(); const params = useParams<{ orgId: string }>(); @@ -467,8 +472,10 @@ export default function SiteResourcesOverview({ key="public" title={t("siteResourcesSectionPublic")} description={t("siteResourcesSectionPublicDescription")} - viewAllHref={publicViewAllHref} - viewAllLabel={t("siteResourcesViewAllPublic")} + viewAllHref={showViewAllLinks ? publicViewAllHref : undefined} + viewAllLabel={ + showViewAllLinks ? t("siteResourcesViewAllPublic") : undefined + } emptyLabel={t("siteResourcesEmptyPublic")} isForbidden={publicForbidden} isFetching={publicQuery.isFetching} @@ -484,8 +491,10 @@ export default function SiteResourcesOverview({ key="private" title={t("siteResourcesSectionPrivate")} description={t("siteResourcesSectionPrivateDescription")} - viewAllHref={privateViewAllHref} - viewAllLabel={t("siteResourcesViewAllPrivate")} + viewAllHref={showViewAllLinks ? privateViewAllHref : undefined} + viewAllLabel={ + showViewAllLinks ? t("siteResourcesViewAllPrivate") : undefined + } emptyLabel={t("siteResourcesEmptyPrivate")} isForbidden={privateForbidden} isFetching={privateQuery.isFetching} From d1b0bbb6d98f6721bdcd8bd52721e5acdd2b1fb1 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 15 Jul 2026 16:05:53 -0400 Subject: [PATCH 27/94] update translations --- messages/en-US.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 549865dcf..9e2175d53 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -472,12 +472,12 @@ "provisioningKeysSave": "Save the provisioning key", "provisioningKeysSaveDescription": "You will only be able to see this once. Copy it to a secure place.", "provisioningKeysErrorCreate": "Error creating provisioning key", - "provisioningKeysList": "New provisioning key", - "provisioningKeysMaxBatchSize": "Max batch size", + "provisioningKeysList": "New Provisioning Key", + "provisioningKeysMaxBatchSize": "Max Batch Size", "provisioningKeysUnlimitedBatchSize": "Unlimited batch size (no limit)", "provisioningKeysMaxBatchUnlimited": "Unlimited", "provisioningKeysMaxBatchSizeInvalid": "Enter a valid max batch size (1–1,000,000).", - "provisioningKeysValidUntil": "Valid until", + "provisioningKeysValidUntil": "Valid Until", "provisioningKeysValidUntilHint": "Leave empty for no expiration.", "provisioningKeysValidUntilInvalid": "Enter a valid date and time.", "provisioningKeysNumUsed": "Times used", @@ -486,7 +486,7 @@ "provisioningKeysNeverUsed": "Never", "provisioningKeysEdit": "Edit Provisioning Key", "provisioningKeysEditDescription": "Update the max batch size and expiration time for this key.", - "provisioningKeysApproveNewSites": "Approve new sites", + "provisioningKeysApproveNewSites": "Approve New Sites", "provisioningKeysApproveNewSitesDescription": "Automatically approve sites that register with this key.", "provisioningKeysUpdateError": "Error updating provisioning key", "provisioningKeysUpdated": "Provisioning key updated", From d4c602cf917bae495d29a5120515dd8dd6e44d99 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 17:21:19 -0400 Subject: [PATCH 28/94] Update the counts when deleting resources --- server/routers/site/deleteSite.ts | 18 ++++++++++++++++++ server/routers/site/rejectSite.ts | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 2126b69d8..6cf584d18 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -166,6 +166,24 @@ export async function deleteSite( site.orgId, trx ); + + if (resourceSideEffects.resources.length > 0) { + await usageService.add( + site.orgId, + LimitId.PUBLIC_RESOURCES, + -resourceSideEffects.resources.length, + trx + ); + } + + if (resourceSideEffects.siteResources.length > 0) { + await usageService.add( + site.orgId, + LimitId.PRIVATE_RESOURCES, + -resourceSideEffects.siteResources.length, + trx + ); + } }); await runDeleteSiteAssociatedResourcesSideEffects( diff --git a/server/routers/site/rejectSite.ts b/server/routers/site/rejectSite.ts index 037879e82..c671dcd79 100644 --- a/server/routers/site/rejectSite.ts +++ b/server/routers/site/rejectSite.ts @@ -129,6 +129,24 @@ export async function rejectSite( site.orgId, trx ); + + if (resourceSideEffects.resources.length > 0) { + await usageService.add( + site.orgId, + LimitId.PUBLIC_RESOURCES, + -resourceSideEffects.resources.length, + trx + ); + } + + if (resourceSideEffects.siteResources.length > 0) { + await usageService.add( + site.orgId, + LimitId.PRIVATE_RESOURCES, + -resourceSideEffects.siteResources.length, + trx + ); + } }); await runDeleteSiteAssociatedResourcesSideEffects(resourceSideEffects); From a7010b2788821821f587823242041db86a8b7c4f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:53:51 -0400 Subject: [PATCH 29/94] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index 9bc4c83a1..d7453592a 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -449,8 +449,14 @@ "provisioningManage": "Aprovisionamiento", "provisioningDescription": "Administrar las claves de aprovisionamiento y revisar los sitios pendientes de aprobación.", "pendingSites": "Sitios pendientes", - "siteApproveSuccess": "Sitio aprobado con éxito", + "siteApproveSuccess": "Sitio y recursos asociados aprobados correctamente", "siteApproveError": "Error al aprobar el sitio", + "siteReject": "Rechazar Sitio", + "siteQuestionReject": "¿Está seguro de que desea rechazar este sitio?", + "siteMessageReject": "Esto eliminará permanentemente el sitio y cualquier recurso asociado que aún esté pendiente.", + "siteConfirmReject": "Confirmar Rechazo del Sitio", + "siteRejectSuccess": "Sitio rechazado correctamente", + "siteRejectError": "Error al rechazar el sitio", "provisioningKeys": "Claves de aprovisionamiento", "searchProvisioningKeys": "Buscar claves de suministro...", "provisioningKeysAdd": "Generar clave de aprovisionamiento", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Guardar la clave de aprovisionamiento", "provisioningKeysSaveDescription": "Sólo podrás verlo una vez. Copítalo a un lugar seguro.", "provisioningKeysErrorCreate": "Error al crear la clave de provisioning", - "provisioningKeysList": "Nueva clave de aprovisionamiento", - "provisioningKeysMaxBatchSize": "Tamaño máximo de lote", + "provisioningKeysList": "Nueva Clave de Provisión", + "provisioningKeysMaxBatchSize": "Tamaño Máximo del Lote", "provisioningKeysUnlimitedBatchSize": "Tamaño ilimitado del lote (sin límite)", "provisioningKeysMaxBatchUnlimited": "Ilimitado", "provisioningKeysMaxBatchSizeInvalid": "Introduzca un tamaño máximo de lote válido (1–1,000,000).", - "provisioningKeysValidUntil": "Válido hasta", + "provisioningKeysValidUntil": "Válido Hasta", "provisioningKeysValidUntilHint": "Dejar vacío para no expirar.", "provisioningKeysValidUntilInvalid": "Introduzca una fecha y hora válidas.", "provisioningKeysNumUsed": "Tiempos usados", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Nunca", "provisioningKeysEdit": "Editar clave de aprovisionamiento", "provisioningKeysEditDescription": "Actualizar el tamaño máximo de lote y el tiempo de caducidad para esta clave.", - "provisioningKeysApproveNewSites": "Aprobar nuevos sitios", + "provisioningKeysApproveNewSites": "Aprobar Nuevos Sitios", "provisioningKeysApproveNewSitesDescription": "Aprobar automáticamente los sitios que se registran con esta clave.", "provisioningKeysUpdateError": "Error al actualizar la clave de aprovisionamiento", "provisioningKeysUpdated": "Clave de aprovisionamiento actualizada", @@ -1397,6 +1403,7 @@ "createOrgUser": "Crear usuario Org", "actionUpdateOrg": "Actualizar organización", "actionRemoveInvitation": "Eliminar invitación", + "actionRemoveUserRole": "Quitar Rol de Usuario", "actionUpdateUser": "Actualizar usuario", "actionGetUser": "Obtener usuario", "actionGetOrgUser": "Obtener usuario de la organización", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Ingrese el token de configuración desde la consola del servidor.", "setupTokenRequired": "Se requiere el token de configuración", "actionUpdateSite": "Actualizar sitio", + "actionApproveSite": "Aprobar Sitio", + "actionRejectSite": "Rechazar Sitio", "actionResetSiteBandwidth": "Restablecer ancho de banda de la organización", "actionListSiteRoles": "Lista de roles permitidos del sitio", "actionCreateResource": "Crear Recurso", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.", "otpAuthSubmit": "Enviar código", "idpContinue": "O continuar con", + "idpLastUsed": "Último Uso", "otpAuthBack": "Volver a la contraseña", "navbar": "Menú de navegación", "navbarDescription": "Menú de navegación principal para la aplicación", From e497fcc3b94950cfe43fe74edb2233060bdb2596 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:53:53 -0400 Subject: [PATCH 30/94] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 2d455afa8..25e192fec 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -449,8 +449,14 @@ "provisioningManage": "Осигуряване", "provisioningDescription": "Управление на ключовете за осигуряване и преглед на чаканещите сайтове за одобрение.", "pendingSites": "Чаканещи сайтове", - "siteApproveSuccess": "Сайтът е одобрен успешно", + "siteApproveSuccess": "Сайтът и свързаните ресурси са одобрени успешно", "siteApproveError": "Грешка при одобряването на сайта", + "siteReject": "Отказ на сайт", + "siteQuestionReject": "Сигурни ли сте, че искате да откажете този сайт?", + "siteMessageReject": "Това ще изтрие окончателно сайта и всички свързани ресурси, които все още са на изчакване.", + "siteConfirmReject": "Потвърдете отказ на сайт", + "siteRejectSuccess": "Сайтът беше успешно отхвърлен", + "siteRejectError": "Грешка при отхвърляне на сайта", "provisioningKeys": "Ключове за осигуряване", "searchProvisioningKeys": "Търсене на ключове за осигуряване...", "provisioningKeysAdd": "Генериране на ключ за осигуряване", @@ -466,8 +472,8 @@ "provisioningKeysSave": "Запазете ключа за осигуряване", "provisioningKeysSaveDescription": "Ще можете да видите това само веднъж. Копирайте го на сигурно място.", "provisioningKeysErrorCreate": "Грешка при създаване на ключ за осигуряване", - "provisioningKeysList": "Нов ключ за осигуряване", - "provisioningKeysMaxBatchSize": "Максимален размер на пакет", + "provisioningKeysList": "Нов ключ за разпределяне", + "provisioningKeysMaxBatchSize": "Максимален размер на пакета", "provisioningKeysUnlimitedBatchSize": "Неограничен размер на партида (без лимит)", "provisioningKeysMaxBatchUnlimited": "Неограничено", "provisioningKeysMaxBatchSizeInvalid": "Въведете валиден максимален размер на партида (1–1,000,000).", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Никога", "provisioningKeysEdit": "Редактиране на ключ за осигуряване", "provisioningKeysEditDescription": "Актуализирайте максималния размер на партида и времето на изтичане за този ключ.", - "provisioningKeysApproveNewSites": "Одобрете нови сайтове", + "provisioningKeysApproveNewSites": "Одобряване на нови сайтове", "provisioningKeysApproveNewSitesDescription": "Автоматично одобряване на сайтове, които се регистрират с този ключ.", "provisioningKeysUpdateError": "Грешка при актуализирането на ключа за осигуряване", "provisioningKeysUpdated": "Ключът за осигуряване е актуализиран", @@ -1397,6 +1403,7 @@ "createOrgUser": "Създаване на Организационна Потребител", "actionUpdateOrg": "Актуализиране на организацията", "actionRemoveInvitation": "Премахване на поканата.", + "actionRemoveUserRole": "Премахване на роля на потребител", "actionUpdateUser": "Актуализиране на потребител", "actionGetUser": "Получаване на потребител", "actionGetOrgUser": "Вземете потребител на организация", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Въведете конфигурационния токен от сървърната конзола.", "setupTokenRequired": "Необходим е конфигурационен токен", "actionUpdateSite": "Актуализиране на сайт", + "actionApproveSite": "Одобряване на сайт", + "actionRejectSite": "Отказ на сайт", "actionResetSiteBandwidth": "Нулиране на честотната лента на организацията", "actionListSiteRoles": "Изброяване на позволените роли за сайта", "actionCreateResource": "Създаване на ресурс", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.", "otpAuthSubmit": "Изпрати код", "idpContinue": "Или продължете със", + "idpLastUsed": "Последно използвано", "otpAuthBack": "Назад към парола", "navbar": "Навигационно меню", "navbarDescription": "Главно навигационно меню за приложението", From 8d1f5e1096946b11031120440dd99b00c1c63556 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:53:55 -0400 Subject: [PATCH 31/94] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index f456ee3b9..f440a8e38 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -449,8 +449,14 @@ "provisioningManage": "Zajištění", "provisioningDescription": "Spravovat klíče pro nastavení a zkontrolovat čekající stránky čekající na schválení.", "pendingSites": "Nevyřízené weby", - "siteApproveSuccess": "Web byl úspěšně schválen", + "siteApproveSuccess": "Stránka a přidružené zdroje byly úspěšně schváleny", "siteApproveError": "Chyba při schvalování webu", + "siteReject": "Odmítnout stránku", + "siteQuestionReject": "Opravdu chcete tuto stránku odmítnout?", + "siteMessageReject": "Toto trvale odstraní stránku a všechny přidružené zdroje, které jsou stále nevyřízené.", + "siteConfirmReject": "Potvrdit odmítnutí stránky", + "siteRejectSuccess": "Stránka byla úspěšně odmítnuta", + "siteRejectError": "Chyba při odmítání stránky", "provisioningKeys": "Poskytovací klíče", "searchProvisioningKeys": "Hledat klíče k zajišťování...", "provisioningKeysAdd": "Generovat zajišťovací klíč", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Uložit konfigurační klíč", "provisioningKeysSaveDescription": "Toto můžete vidět pouze jednou. Zkopírujte ho na bezpečné místo.", "provisioningKeysErrorCreate": "Chyba při vytváření doplňovacího klíče", - "provisioningKeysList": "Nový klíč pro poskytování informací", + "provisioningKeysList": "Nový provisioning klíč", "provisioningKeysMaxBatchSize": "Maximální velikost dávky", "provisioningKeysUnlimitedBatchSize": "Neomezená velikost šarže (bez omezení)", "provisioningKeysMaxBatchUnlimited": "Bez omezení", "provisioningKeysMaxBatchSizeInvalid": "Zadejte platnou maximální velikost šarže (1–1,000,000).", - "provisioningKeysValidUntil": "Platné do", + "provisioningKeysValidUntil": "Platná do", "provisioningKeysValidUntilHint": "Ponechte prázdné, pokud vyprší platnost.", "provisioningKeysValidUntilInvalid": "Zadejte platné datum a čas.", "provisioningKeysNumUsed": "Časy použití", @@ -1397,6 +1403,7 @@ "createOrgUser": "Vytvořit Org uživatele", "actionUpdateOrg": "Aktualizovat organizaci", "actionRemoveInvitation": "Odstranit pozvání", + "actionRemoveUserRole": "Odstranit uživatelskou roli", "actionUpdateUser": "Aktualizovat uživatele", "actionGetUser": "Získat uživatele", "actionGetOrgUser": "Získat uživatele organizace", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Zadejte nastavovací token z konzole serveru.", "setupTokenRequired": "Je vyžadován token nastavení", "actionUpdateSite": "Aktualizovat stránku", + "actionApproveSite": "Schválit stránku", + "actionRejectSite": "Odmítnout stránku", "actionResetSiteBandwidth": "Resetovat šířku pásma organizace", "actionListSiteRoles": "Seznam povolených rolí webu", "actionCreateResource": "Vytvořit zdroj", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.", "otpAuthSubmit": "Odeslat kód", "idpContinue": "Nebo pokračovat s", + "idpLastUsed": "Naposled použito", "otpAuthBack": "Zpět na heslo", "navbar": "Navigation Menu", "navbarDescription": "Hlavní navigační menu aplikace", From 00f7510195109633cb7c8ff86b3a7ac1065743a9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:53:57 -0400 Subject: [PATCH 32/94] New translations en-us.json (Danish) [ci skip] --- messages/da-DK.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/messages/da-DK.json b/messages/da-DK.json index ce08bff23..2e1376264 100644 --- a/messages/da-DK.json +++ b/messages/da-DK.json @@ -449,8 +449,14 @@ "provisioningManage": "Provisionering", "provisioningDescription": "Administrer provisioneringsnøgler og gennemgå ventende sites som venter på godkendelse.", "pendingSites": "Ventende sites", - "siteApproveSuccess": "Med succes godkendelse af site", + "siteApproveSuccess": "Site og tilknyttede ressourcer godkendt med succes", "siteApproveError": "Fejl ved godkendelse af side", + "siteReject": "Afvis Site", + "siteQuestionReject": "Er du sikker på, at du vil afvise dette site?", + "siteMessageReject": "Dette vil permanent slette sitet og enhver tilknyttet ressource, der stadig er i afventning.", + "siteConfirmReject": "Bekræft Afvis Site", + "siteRejectSuccess": "Site afvist med succes", + "siteRejectError": "Fejl ved afvisning af sitet", "provisioningKeys": "Provisioneringsnøgler", "searchProvisioningKeys": "Søg varer i provisioneringsnøgler...", "provisioningKeysAdd": "Generer provisioneringsnøgle", @@ -467,7 +473,7 @@ "provisioningKeysSaveDescription": "Du kan kun se denne én gang. Kopiér det til et sikkert sted.", "provisioningKeysErrorCreate": "Fejl under oprettelse af provisioneringsnøgle", "provisioningKeysList": "Ny provisioneringsnøgle", - "provisioningKeysMaxBatchSize": "Maks størrelse på bunt", + "provisioningKeysMaxBatchSize": "Maksimum Batch Størrelse", "provisioningKeysUnlimitedBatchSize": "Ubegrænset mengde bunt (ingen begrænsning)", "provisioningKeysMaxBatchUnlimited": "Ubegrænset", "provisioningKeysMaxBatchSizeInvalid": "Angiv en gyldig sjakkstørrelse (1–1 000.000).", @@ -1397,6 +1403,7 @@ "createOrgUser": "Opret organisationsbruger", "actionUpdateOrg": "Opdater organisation", "actionRemoveInvitation": "Fjern invitation", + "actionRemoveUserRole": "Fjern Brugers Rolle", "actionUpdateUser": "Opdater bruger", "actionGetUser": "Hent bruger", "actionGetOrgUser": "Hent organisationsbruger", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Indtast opsætningstoken fra serverkonsollen.", "setupTokenRequired": "Opsætningstoken er nødvendig", "actionUpdateSite": "Opdater site", + "actionApproveSite": "Godkend Site", + "actionRejectSite": "Afvis Site", "actionResetSiteBandwidth": "Nulstil organisationsbåndbredde", "actionListSiteRoles": "Vis tilladte områderoller", "actionCreateResource": "Opret ressource", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Indtast koden fra autentiseringsappen din eller en af dine engangs reservekoder.", "otpAuthSubmit": "Send kode", "idpContinue": "Eller fortsæt med", + "idpLastUsed": "Sidst brugt", "otpAuthBack": "Tilbage til adgangskode", "navbar": "Navigationsmenu", "navbarDescription": "Hovednavigasjonsmeny for applikationen", From e6646e5833ded21fd6cd5d6b22ad1dfd15c2847a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:53:59 -0400 Subject: [PATCH 33/94] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index dc7dbb808..3ceac6dc4 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -449,8 +449,14 @@ "provisioningManage": "Bereitstellung", "provisioningDescription": "Bereitstellungsschlüssel verwalten und ausstehende Standorte prüfen, die noch auf Genehmigung warten.", "pendingSites": "Ausstehende Standorte", - "siteApproveSuccess": "Standort erfolgreich freigegeben", + "siteApproveSuccess": "Site und zugehörige Ressourcen erfolgreich genehmigt", "siteApproveError": "Fehler beim Genehmigen des Standorts", + "siteReject": "Site ablehnen", + "siteQuestionReject": "Sind Sie sicher, dass Sie diese Site ablehnen möchten?", + "siteMessageReject": "Dies wird die Site und alle damit verbundenen, noch ausstehenden Ressourcen dauerhaft löschen.", + "siteConfirmReject": "Ablehnung der Site bestätigen", + "siteRejectSuccess": "Site erfolgreich abgelehnt", + "siteRejectError": "Fehler beim Ablehnen der Site", "provisioningKeys": "Bereitstellungsschlüssel", "searchProvisioningKeys": "Bereitstellungsschlüssel suchen...", "provisioningKeysAdd": "Bereitstellungsschlüssel generieren", @@ -467,7 +473,7 @@ "provisioningKeysSaveDescription": "Sie können dies nur einmal sehen. Kopieren Sie es an einen sicheren Ort.", "provisioningKeysErrorCreate": "Fehler beim Erstellen des Bereitstellungsschlüssels", "provisioningKeysList": "Neuer Bereitstellungsschlüssel", - "provisioningKeysMaxBatchSize": "Max. Batch-Größe", + "provisioningKeysMaxBatchSize": "Maximale Batch-Größe", "provisioningKeysUnlimitedBatchSize": "Unbegrenzte Batch-Größe (kein Limit)", "provisioningKeysMaxBatchUnlimited": "Unbegrenzt", "provisioningKeysMaxBatchSizeInvalid": "Geben Sie eine gültige maximale Batchgröße ein (1–1.000.000).", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Nie", "provisioningKeysEdit": "Bereitstellungsschlüssel bearbeiten", "provisioningKeysEditDescription": "Aktualisieren Sie die maximale Batch-Größe und Ablaufzeit für diesen Schlüssel.", - "provisioningKeysApproveNewSites": "Neuen Standort genehmigen", + "provisioningKeysApproveNewSites": "Neue Sites genehmigen", "provisioningKeysApproveNewSitesDescription": "Sites, die sich mit diesem Schlüssel registrieren, automatisch freigeben.", "provisioningKeysUpdateError": "Fehler beim Aktualisieren des Bereitstellungsschlüssels", "provisioningKeysUpdated": "Bereitstellungsschlüssel aktualisiert", @@ -1397,6 +1403,7 @@ "createOrgUser": "Org Benutzer erstellen", "actionUpdateOrg": "Organisation aktualisieren", "actionRemoveInvitation": "Einladung entfernen", + "actionRemoveUserRole": "Benutzerrolle entfernen", "actionUpdateUser": "Benutzer aktualisieren", "actionGetUser": "Benutzer abrufen", "actionGetOrgUser": "Organisationsbenutzer abrufen", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Geben Sie das Setup-Token von der Serverkonsole ein.", "setupTokenRequired": "Setup-Token ist erforderlich", "actionUpdateSite": "Standorte aktualisieren", + "actionApproveSite": "Site genehmigen", + "actionRejectSite": "Site ablehnen", "actionResetSiteBandwidth": "Organisations-Bandbreite zurücksetzen", "actionListSiteRoles": "Erlaubte Standort-Rollen auflisten", "actionCreateResource": "Ressource erstellen", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.", "otpAuthSubmit": "Code absenden", "idpContinue": "Oder weiter mit", + "idpLastUsed": "Zuletzt verwendet", "otpAuthBack": "Zurück zum Passwort", "navbar": "Navigationsmenü", "navbarDescription": "Hauptnavigationsmenü für die Anwendung", From cebbf9ca65c27f7a470c945a2fa47d27816dcab4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:00 -0400 Subject: [PATCH 34/94] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index ebb398334..5edde2e2e 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -449,8 +449,14 @@ "provisioningManage": "Accantonamento", "provisioningDescription": "Gestire le chiavi di provisioning e rivedere i siti in attesa di approvazione.", "pendingSites": "Siti In Attesa", - "siteApproveSuccess": "Sito approvato con successo", + "siteApproveSuccess": "Sito e risorse associate approvate con successo", "siteApproveError": "Errore nell'approvazione del sito", + "siteReject": "Rifiuta Sito", + "siteQuestionReject": "Sei sicuro di voler rifiutare questo sito?", + "siteMessageReject": "Questo eliminerà permanentemente il sito e tutte le risorse associate ancora in sospeso.", + "siteConfirmReject": "Conferma Rifiuto Sito", + "siteRejectSuccess": "Sito rifiutato con successo", + "siteRejectError": "Errore nel rifiutare il sito", "provisioningKeys": "Chiavi Di Provvedimento", "searchProvisioningKeys": "Cerca le chiavi di provisioning...", "provisioningKeysAdd": "Genera Chiave di provisioning", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Salva la chiave di provisioning", "provisioningKeysSaveDescription": "Sarai in grado di vedere solo una volta. Copiarlo in un posto sicuro.", "provisioningKeysErrorCreate": "Errore nella creazione della chiave di provisioning", - "provisioningKeysList": "Nuova chiave di provisioning", - "provisioningKeysMaxBatchSize": "Dimensione massima batch", + "provisioningKeysList": "Nuova Chiave di Provisioning", + "provisioningKeysMaxBatchSize": "Dimensione Massima Batch", "provisioningKeysUnlimitedBatchSize": "Dimensione illimitata del batch (nessun limite)", "provisioningKeysMaxBatchUnlimited": "Illimitato", "provisioningKeysMaxBatchSizeInvalid": "Inserisci una dimensione massima valida del batch (1–1.000.000).", - "provisioningKeysValidUntil": "Valido fino al", + "provisioningKeysValidUntil": "Valido Fino a", "provisioningKeysValidUntilHint": "Lasciare vuoto per nessuna scadenza.", "provisioningKeysValidUntilInvalid": "Inserisci una data e ora valide.", "provisioningKeysNumUsed": "Volte usate", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Mai", "provisioningKeysEdit": "Modifica Chiave di provisioning", "provisioningKeysEditDescription": "Aggiorna la dimensione massima del batch e il tempo di scadenza per questa chiave.", - "provisioningKeysApproveNewSites": "Approva nuovi siti", + "provisioningKeysApproveNewSites": "Approva Nuovi Siti", "provisioningKeysApproveNewSitesDescription": "Approvare automaticamente i siti che si registrano con questa chiave.", "provisioningKeysUpdateError": "Errore nell'aggiornamento della chiave di provisioning", "provisioningKeysUpdated": "Chiave di provisioning aggiornata", @@ -1397,6 +1403,7 @@ "createOrgUser": "Crea Utente Org", "actionUpdateOrg": "Aggiorna Organizzazione", "actionRemoveInvitation": "Rimuovi Invito", + "actionRemoveUserRole": "Rimuovi Ruolo Utente", "actionUpdateUser": "Aggiorna Utente", "actionGetUser": "Ottieni Utente", "actionGetOrgUser": "Ottieni Utente Organizzazione", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Inserisci il token di configurazione dalla console del server.", "setupTokenRequired": "Il token di configurazione è richiesto", "actionUpdateSite": "Aggiorna Sito", + "actionApproveSite": "Approva Sito", + "actionRejectSite": "Rifiuta Sito", "actionResetSiteBandwidth": "Reimposta Larghezza Banda Dell'Organizzazione", "actionListSiteRoles": "Elenca Ruoli Sito Consentiti", "actionCreateResource": "Crea Risorsa", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.", "otpAuthSubmit": "Invia Codice", "idpContinue": "O continua con", + "idpLastUsed": "Ultimo Utilizzo", "otpAuthBack": "Torna alla Password", "navbar": "Menu di Navigazione", "navbarDescription": "Menu di navigazione principale dell'applicazione", From 6c1e1bc0ca576fafc311c4cde782d1c7344cec46 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:02 -0400 Subject: [PATCH 35/94] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index d8c929ead..67c0ee59f 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -449,8 +449,14 @@ "provisioningManage": "프로비저닝", "provisioningDescription": "프로비저닝 키를 관리하고 승인을 기다리는 사이트를 검토합니다.", "pendingSites": "대기중인 사이트", - "siteApproveSuccess": "사이트가 성공적으로 승인되었습니다", + "siteApproveSuccess": "사이트 및 관련 리소스가 성공적으로 승인되었습니다", "siteApproveError": "사이트 승인 오류", + "siteReject": "사이트 거부", + "siteQuestionReject": "이 사이트를 거부하시겠습니까?", + "siteMessageReject": "이렇게 하면 펜딩 중인 사이트 및 관련 리소스가 영구적으로 삭제됩니다.", + "siteConfirmReject": "사이트 거부 확인", + "siteRejectSuccess": "사이트가 성공적으로 거부되었습니다", + "siteRejectError": "사이트 거부 오류", "provisioningKeys": "프로비저닝 키", "searchProvisioningKeys": "프로비저닝 키 검색...", "provisioningKeysAdd": "프로비저닝 키 생성", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "절대", "provisioningKeysEdit": "프로비저닝 키 수정", "provisioningKeysEditDescription": "이 키의 최대 배치 크기 및 만료 시간을 업데이트하세요.", - "provisioningKeysApproveNewSites": "새로운 사이트 승인", + "provisioningKeysApproveNewSites": "새 사이트 승인", "provisioningKeysApproveNewSitesDescription": "이 키를 등록하는 사이트를 자동으로 승인합니다.", "provisioningKeysUpdateError": "프로비저닝 키 업데이트 오류", "provisioningKeysUpdated": "프로비저닝 키가 업데이트되었습니다", @@ -1397,6 +1403,7 @@ "createOrgUser": "조직 사용자 생성", "actionUpdateOrg": "조직 업데이트", "actionRemoveInvitation": "초대 제거", + "actionRemoveUserRole": "사용자 역할 제거", "actionUpdateUser": "사용자 업데이트", "actionGetUser": "사용자 조회", "actionGetOrgUser": "조직 사용자 가져오기", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "서버 콘솔에서 설정 토큰 입력.", "setupTokenRequired": "설정 토큰이 필요합니다", "actionUpdateSite": "사이트 업데이트", + "actionApproveSite": "사이트 승인", + "actionRejectSite": "사이트 거부", "actionResetSiteBandwidth": "조직 대역폭 재설정", "actionListSiteRoles": "허용된 사이트 역할 목록", "actionCreateResource": "리소스 생성", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.", "otpAuthSubmit": "코드 제출", "idpContinue": "또는 계속 진행하십시오.", + "idpLastUsed": "마지막 사용", "otpAuthBack": "비밀번호로 돌아가기", "navbar": "탐색 메뉴", "navbarDescription": "애플리케이션의 주요 탐색 메뉴", From 40e05f903a8ace9d1de4e80c3ac78a1043518305 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:04 -0400 Subject: [PATCH 36/94] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 506a4a5b3..9239cd81c 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -449,8 +449,14 @@ "provisioningManage": "Provisie", "provisioningDescription": "Voorzieningssleutels beheren en sites beoordelen in afwachting van goedkeuring.", "pendingSites": "Openstaande sites", - "siteApproveSuccess": "Site succesvol goedgekeurd", + "siteApproveSuccess": "Site en bijbehorende middelen succesvol goedgekeurd", "siteApproveError": "Fout bij goedkeuren website", + "siteReject": "Afwijzen Site", + "siteQuestionReject": "Weet u zeker dat u deze site wilt afwijzen?", + "siteMessageReject": "Hiermee wordt de site en alle bijbehorende middelen die nog in behandeling zijn permanent verwijderd.", + "siteConfirmReject": "Afwijzing van site bevestigen", + "siteRejectSuccess": "Site succesvol afgewezen", + "siteRejectError": "Fout bij het afwijzen van site", "provisioningKeys": "Verhelderende sleutels", "searchProvisioningKeys": "Zoek provisioningsleutels ...", "provisioningKeysAdd": "Genereer Provisioning Sleutel", @@ -466,7 +472,7 @@ "provisioningKeysSave": "Sla de provisioning sleutel op", "provisioningKeysSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een veilige plaats.", "provisioningKeysErrorCreate": "Fout bij aanmaken provisioning sleutel", - "provisioningKeysList": "Nieuwe provisioning sleutel", + "provisioningKeysList": "Nieuwe provisioning-sleutel", "provisioningKeysMaxBatchSize": "Maximale batchgrootte", "provisioningKeysUnlimitedBatchSize": "Onbeperkte batchgrootte (geen limiet)", "provisioningKeysMaxBatchUnlimited": "Onbeperkt", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Nooit", "provisioningKeysEdit": "Wijzig Provisioning Sleutel", "provisioningKeysEditDescription": "Werk de maximale batchgrootte en verlooptijd voor deze sleutel bij.", - "provisioningKeysApproveNewSites": "Goedkeuren van nieuwe sites", + "provisioningKeysApproveNewSites": "Nieuwe sites goedkeuren", "provisioningKeysApproveNewSitesDescription": "Automatisch sites goedkeuren die zich registreren met deze sleutel.", "provisioningKeysUpdateError": "Fout tijdens bijwerken provisioning sleutel", "provisioningKeysUpdated": "Provisie sleutel bijgewerkt", @@ -1397,6 +1403,7 @@ "createOrgUser": "Org gebruiker aanmaken", "actionUpdateOrg": "Organisatie bijwerken", "actionRemoveInvitation": "Verwijder uitnodiging", + "actionRemoveUserRole": "Gebruikersrol verwijderen", "actionUpdateUser": "Gebruiker bijwerken", "actionGetUser": "Gebruiker ophalen", "actionGetOrgUser": "Krijg organisatie-gebruiker", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.", "setupTokenRequired": "Setup-token is vereist", "actionUpdateSite": "Site bijwerken", + "actionApproveSite": "Verleen site goedkeuring", + "actionRejectSite": "Afwijzen Site", "actionResetSiteBandwidth": "Reset organisatieschandbreedte", "actionListSiteRoles": "Toon toegestane sitenollen", "actionCreateResource": "Bron maken", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.", "otpAuthSubmit": "Code indienen", "idpContinue": "Of ga verder met", + "idpLastUsed": "Laatst gebruikt", "otpAuthBack": "Terug naar wachtwoord", "navbar": "Navigatiemenu", "navbarDescription": "Hoofd navigatie menu voor de applicatie", From 22a5743817effe1d82370fbf9a74ce28f9193de6 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:06 -0400 Subject: [PATCH 37/94] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 718579932..11df1b353 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -449,8 +449,14 @@ "provisioningManage": "Dostarczanie", "provisioningDescription": "Zarządzaj kluczami rezerwacji i sprawdzaj oczekujące strony oczekujące na zatwierdzenie.", "pendingSites": "Witryny oczekujące", - "siteApproveSuccess": "Witryna została pomyślnie zatwierdzona", + "siteApproveSuccess": "Witryna i powiązane zasoby zostały zatwierdzone pomyślnie", "siteApproveError": "Błąd zatwierdzania witryny", + "siteReject": "Odrzuć witrynę", + "siteQuestionReject": "Czy na pewno chcesz odrzucić tę witrynę?", + "siteMessageReject": "Spowoduje to trwałe usunięcie witryny i wszelkich powiązanych, nadal oczekujących zasobów.", + "siteConfirmReject": "Potwierdź odrzucenie witryny", + "siteRejectSuccess": "Witryna została odrzucona pomyślnie", + "siteRejectError": "Błąd podczas odrzucania witryny", "provisioningKeys": "Klucze Zaopatrzenia", "searchProvisioningKeys": "Szukaj kluczy zaopatrzenia...", "provisioningKeysAdd": "Wygeneruj klucz zaopatrzenia", @@ -466,7 +472,7 @@ "provisioningKeysSave": "Zapisz klucz zaopatrzenia", "provisioningKeysSaveDescription": "Możesz to zobaczyć tylko raz. Skopiuj je do bezpiecznego miejsca.", "provisioningKeysErrorCreate": "Błąd podczas tworzenia klucza zaopatrzenia", - "provisioningKeysList": "Nowy klucz rezerwacji", + "provisioningKeysList": "Nowy klucz aprowizacyjny", "provisioningKeysMaxBatchSize": "Maksymalny rozmiar partii", "provisioningKeysUnlimitedBatchSize": "Nieograniczony rozmiar partii (bez limitu)", "provisioningKeysMaxBatchUnlimited": "Nieograniczona", @@ -1397,6 +1403,7 @@ "createOrgUser": "Utwórz użytkownika Org", "actionUpdateOrg": "Aktualizuj organizację", "actionRemoveInvitation": "Usuń zaproszenie", + "actionRemoveUserRole": "Usuń rolę użytkownika", "actionUpdateUser": "Zaktualizuj użytkownika", "actionGetUser": "Pobierz użytkownika", "actionGetOrgUser": "Pobierz użytkownika organizacji", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Wprowadź token konfiguracji z konsoli serwera.", "setupTokenRequired": "Wymagany jest token konfiguracji", "actionUpdateSite": "Aktualizuj witrynę", + "actionApproveSite": "Zatwierdź witrynę", + "actionRejectSite": "Odrzuć witrynę", "actionResetSiteBandwidth": "Zresetuj przepustowość organizacji", "actionListSiteRoles": "Lista dozwolonych ról witryny", "actionCreateResource": "Utwórz zasób", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.", "otpAuthSubmit": "Wyślij kod", "idpContinue": "Lub kontynuuj z", + "idpLastUsed": "Ostatnio używany", "otpAuthBack": "Powrót do hasła", "navbar": "Menu nawigacyjne", "navbarDescription": "Główne menu nawigacyjne aplikacji", From 4e5fd2f2b069a1f848b189a65cb8571dee07e0de Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:08 -0400 Subject: [PATCH 38/94] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 4084ceae8..ec24ec4d1 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -449,8 +449,14 @@ "provisioningManage": "Mise en place", "provisioningDescription": "Gérer les clés de provisioning et examiner les sites en attente d'approbation.", "pendingSites": "Sites en attente", - "siteApproveSuccess": "Site approuvé avec succès", + "siteApproveSuccess": "Site et ressources associées approuvés avec succès", "siteApproveError": "Erreur lors de l'approbation du site", + "siteReject": "Rejeter le site", + "siteQuestionReject": "Êtes-vous sûr de vouloir rejeter ce site ?", + "siteMessageReject": "Cela supprimera définitivement le site et toutes les ressources associées qui sont encore en attente.", + "siteConfirmReject": "Confirmer le rejet du site", + "siteRejectSuccess": "Site rejeté avec succès", + "siteRejectError": "Erreur lors du rejet du site", "provisioningKeys": "Clés de provisionnement", "searchProvisioningKeys": "Recherche des clés de provision...", "provisioningKeysAdd": "Générer une clé de provisioning", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Enregistrer la clé de provisioning", "provisioningKeysSaveDescription": "Vous ne pourrez voir cela qu'une seule fois. Copiez-le dans un endroit sécurisé.", "provisioningKeysErrorCreate": "Erreur lors de la création de la clé de provisioning", - "provisioningKeysList": "Nouvelle clé de provisioning", + "provisioningKeysList": "Nouvelle clé d'approvisionnement", "provisioningKeysMaxBatchSize": "Taille maximale du lot", "provisioningKeysUnlimitedBatchSize": "Taille de lot illimitée (sans limite)", "provisioningKeysMaxBatchUnlimited": "Illimité", "provisioningKeysMaxBatchSizeInvalid": "Entrez une taille de lot maximale valide (1–1 000 000).", - "provisioningKeysValidUntil": "Valable jusqu'au", + "provisioningKeysValidUntil": "Valable jusqu'à", "provisioningKeysValidUntilHint": "Laisser vide pour ne pas expirer.", "provisioningKeysValidUntilInvalid": "Entrez une date et une heure valides.", "provisioningKeysNumUsed": "Nombre de fois utilisées", @@ -1397,6 +1403,7 @@ "createOrgUser": "Créer un utilisateur Org", "actionUpdateOrg": "Mettre à jour l'organisation", "actionRemoveInvitation": "Supprimer l'invitation", + "actionRemoveUserRole": "Supprimer le rôle d'utilisateur", "actionUpdateUser": "Mettre à jour l'utilisateur", "actionGetUser": "Obtenir l'utilisateur", "actionGetOrgUser": "Obtenir l'utilisateur de l'organisation", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Entrez le jeton de configuration depuis la console du serveur.", "setupTokenRequired": "Le jeton de configuration est requis.", "actionUpdateSite": "Mettre à jour un site", + "actionApproveSite": "Approuver le site", + "actionRejectSite": "Rejeter le site", "actionResetSiteBandwidth": "Réinitialiser la bande passante de l'organisation", "actionListSiteRoles": "Lister les rôles autorisés du site", "actionCreateResource": "Créer une ressource", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.", "otpAuthSubmit": "Soumettre le code", "idpContinue": "Ou continuer avec", + "idpLastUsed": "Dernière utilisation", "otpAuthBack": "Retour au mot de passe", "navbar": "Menu de navigation", "navbarDescription": "Menu de navigation principal de l'application", From 89f4f4ba6f524b95d04649967419477829f12454 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:10 -0400 Subject: [PATCH 39/94] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index c0d65e0fb..4a37fb52b 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -449,8 +449,14 @@ "provisioningManage": "Provisionamento", "provisioningDescription": "Gerenciar chaves de provisionamento e revisar sites pendentes aguardando aprovação.", "pendingSites": "Sites pendentes", - "siteApproveSuccess": "Site aprovado com sucesso", + "siteApproveSuccess": "Site e recursos associados aprovados com sucesso", "siteApproveError": "Erro ao aprovar site", + "siteReject": "Rejeitar Site", + "siteQuestionReject": "Tem certeza de que deseja rejeitar este site?", + "siteMessageReject": "Isto eliminará permanentemente o site e quaisquer recursos associados que ainda estejam pendentes.", + "siteConfirmReject": "Confirmar Rejeição de Site", + "siteRejectSuccess": "Site rejeitado com sucesso", + "siteRejectError": "Erro ao rejeitar site", "provisioningKeys": "Posicionando chaves", "searchProvisioningKeys": "Pesquisar chaves de provisionamento...", "provisioningKeysAdd": "Gerar chave de provisionamento", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Salvar a chave de provisionamento", "provisioningKeysSaveDescription": "Você só será capaz de ver esta vez. Copiá-lo para um lugar seguro.", "provisioningKeysErrorCreate": "Erro ao criar chave de provisionamento", - "provisioningKeysList": "Nova chave de aprovisionamento", - "provisioningKeysMaxBatchSize": "Tamanho máximo do lote", + "provisioningKeysList": "Nova Chave de Provisionamento", + "provisioningKeysMaxBatchSize": "Tamanho Máximo do Lote", "provisioningKeysUnlimitedBatchSize": "Tamanho ilimitado em lote (sem limite)", "provisioningKeysMaxBatchUnlimited": "Ilimitado", "provisioningKeysMaxBatchSizeInvalid": "Informe um tamanho máximo válido em lote (1–1,000,000).", - "provisioningKeysValidUntil": "Valido ate", + "provisioningKeysValidUntil": "Válido Até", "provisioningKeysValidUntilHint": "Deixe em branco para nenhuma expiração.", "provisioningKeysValidUntilInvalid": "Informe uma data e hora válidas.", "provisioningKeysNumUsed": "Use percentual", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "nunca", "provisioningKeysEdit": "Editar chave de provisionamento", "provisioningKeysEditDescription": "Atualizar o tamanho máximo do lote e tempo de expiração para esta chave.", - "provisioningKeysApproveNewSites": "Aprovar novos sites", + "provisioningKeysApproveNewSites": "Aprovar Novos Sites", "provisioningKeysApproveNewSitesDescription": "Aprovar automaticamente sites que se registram com esta chave.", "provisioningKeysUpdateError": "Erro ao atualizar chave de provisionamento", "provisioningKeysUpdated": "Chave de provisionamento atualizada", @@ -1397,6 +1403,7 @@ "createOrgUser": "Criar utilizador Org", "actionUpdateOrg": "Atualizar Organização", "actionRemoveInvitation": "Remover Convite", + "actionRemoveUserRole": "Remover Função de Utilizador", "actionUpdateUser": "Atualizar Usuário", "actionGetUser": "Obter Usuário", "actionGetOrgUser": "Obter Utilizador da Organização", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Digite o token de configuração do console do servidor.", "setupTokenRequired": "Token de configuração é necessário", "actionUpdateSite": "Atualizar Site", + "actionApproveSite": "Aprovar Site", + "actionRejectSite": "Rejeitar Site", "actionResetSiteBandwidth": "Redefinir banda da organização", "actionListSiteRoles": "Listar Funções Permitidas do Site", "actionCreateResource": "Criar Recurso", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.", "otpAuthSubmit": "Submeter Código", "idpContinue": "Ou continuar com", + "idpLastUsed": "Última Utilização", "otpAuthBack": "Voltar à Palavra-passe", "navbar": "Menu de Navegação", "navbarDescription": "Menu de navegação principal da aplicação", From 5ee406c92ebe0266323879e865e20447193f4526 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:12 -0400 Subject: [PATCH 40/94] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 53bf7b015..efd5bee64 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -449,8 +449,14 @@ "provisioningManage": "Подготовка", "provisioningDescription": "Управляйте предоставленными ключами и проверять непроверенные сайты, ожидающие утверждения.", "pendingSites": "Ожидающие сайты", - "siteApproveSuccess": "Сайт успешно утвержден", + "siteApproveSuccess": "Сайт и связанные ресурсы успешно одобрены", "siteApproveError": "Ошибка при утверждении сайта", + "siteReject": "Отклонить сайт", + "siteQuestionReject": "Вы уверены, что хотите отклонить этот сайт?", + "siteMessageReject": "Этот процесс окончательно удалит сайт и все связанные с ним ресурсы, которые еще ожидают.", + "siteConfirmReject": "Подтвердите отклонение сайта", + "siteRejectSuccess": "Сайт успешно отклонен", + "siteRejectError": "Ошибка при отклонении сайта", "provisioningKeys": "Ключи подготовки", "searchProvisioningKeys": "Поиск подготовительных ключей...", "provisioningKeysAdd": "Сгенерировать ключ подготовки", @@ -466,8 +472,8 @@ "provisioningKeysSave": "Сохранить ключ подготовки", "provisioningKeysSaveDescription": "Вы сможете увидеть это только один раз. Скопируйте его в безопасное место.", "provisioningKeysErrorCreate": "Ошибка при создании ключа подготовки", - "provisioningKeysList": "Новый подготовительный ключ", - "provisioningKeysMaxBatchSize": "Макс. размер партии", + "provisioningKeysList": "Новый ключ для подготовки", + "provisioningKeysMaxBatchSize": "Максимальный размер партии", "provisioningKeysUnlimitedBatchSize": "Неограниченный размер партии (без ограничений)", "provisioningKeysMaxBatchUnlimited": "Неограниченный", "provisioningKeysMaxBatchSizeInvalid": "Введите максимальный размер пакета (1–1,000,000).", @@ -1397,6 +1403,7 @@ "createOrgUser": "Создать пользователя Org", "actionUpdateOrg": "Обновить организацию", "actionRemoveInvitation": "Удалить приглашение", + "actionRemoveUserRole": "Удалить роль пользователя", "actionUpdateUser": "Обновить пользователя", "actionGetUser": "Получить пользователя", "actionGetOrgUser": "Получить пользователя организации", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Введите токен настройки из консоли сервера.", "setupTokenRequired": "Токен настройки обязателен", "actionUpdateSite": "Обновить сайт", + "actionApproveSite": "Одобрить сайт", + "actionRejectSite": "Отклонить сайт", "actionResetSiteBandwidth": "Сброс пропускной способности организации", "actionListSiteRoles": "Список разрешенных ролей сайта", "actionCreateResource": "Создать ресурс", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.", "otpAuthSubmit": "Отправить код", "idpContinue": "Или продолжить с", + "idpLastUsed": "Последнее использование", "otpAuthBack": "Назад к паролю", "navbar": "Навигационное меню", "navbarDescription": "Главное навигационное меню приложения", From e8ed8fc7e9c69bbb8ac1f74a24e3d2de3b7d074c Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:13 -0400 Subject: [PATCH 41/94] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 6ab5d026d..006180cc0 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -449,8 +449,14 @@ "provisioningManage": "Tedarik", "provisioningDescription": "Tedarik anahtarlarını yönetin ve onay bekleyen siteleri gözden geçirin.", "pendingSites": "Bekleyen Siteler", - "siteApproveSuccess": "Site başarıyla onaylandı", + "siteApproveSuccess": "Site ve ilgili kaynaklar başarıyla onaylandı", "siteApproveError": "Site onaylanırken hata oluştu", + "siteReject": "Siteyi Reddet", + "siteQuestionReject": "Bu siteyi reddetmek istediğinizden emin misiniz?", + "siteMessageReject": "Bu işlem, siteyi ve hala beklemede olan herhangi bir ilgili kaynağı kalıcı olarak silecektir.", + "siteConfirmReject": "Site Reddetmeyi Onayla", + "siteRejectSuccess": "Site başarıyla reddedildi", + "siteRejectError": "Site reddedilirken hata oluştu", "provisioningKeys": "Tedarik Anahtarları", "searchProvisioningKeys": "Tedarik anahtarlarını ara...", "provisioningKeysAdd": "Tedarik Anahtarı Üret", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Tedarik anahtarını kaydet", "provisioningKeysSaveDescription": "Bunu yalnızca bir kez görebileceksiniz. Güvenli bir yere kopyalayın.", "provisioningKeysErrorCreate": "Tedarik anahtarı oluşturulurken hata oluştu", - "provisioningKeysList": "Yeni tedarik anahtarı", - "provisioningKeysMaxBatchSize": "Maksimum toplu iş boyutu", + "provisioningKeysList": "Yeni Sağlama Anahtarı", + "provisioningKeysMaxBatchSize": "Maksimum Toplu İş Boyutu", "provisioningKeysUnlimitedBatchSize": "Sınırsız toplu iş boyutu (sınırlama yok)", "provisioningKeysMaxBatchUnlimited": "Sınırsız", "provisioningKeysMaxBatchSizeInvalid": "Geçerli bir maksimum toplu iş boyutu girin (1–1,000,000).", - "provisioningKeysValidUntil": "Geçerlilik tarihi", + "provisioningKeysValidUntil": "Geçerli Olma Süresi", "provisioningKeysValidUntilHint": "Son kullanım tarihi için boş bırakın.", "provisioningKeysValidUntilInvalid": "Geçerli bir tarih ve saat girin.", "provisioningKeysNumUsed": "Kullanım Sayısı", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Asla", "provisioningKeysEdit": "Tedarik Anahtarını Düzenle", "provisioningKeysEditDescription": "Bu anahtar için maksimum toplu iş boyutunu ve son kullanma zamanını güncelleyin.", - "provisioningKeysApproveNewSites": "Yeni siteleri onayla", + "provisioningKeysApproveNewSites": "Yeni Siteleri Onayla", "provisioningKeysApproveNewSitesDescription": "Bu anahtar ile kayıt olan siteleri otomatik olarak onayla.", "provisioningKeysUpdateError": "Tedarik anahtarı güncellenirken hata oluştu", "provisioningKeysUpdated": "Tedarik anahtarı güncellendi", @@ -1397,6 +1403,7 @@ "createOrgUser": "Organizasyon Kullanıcısı Oluştur", "actionUpdateOrg": "Kuruluşu Güncelle", "actionRemoveInvitation": "Daveti Kaldır", + "actionRemoveUserRole": "Kullanıcı Rolünü Kaldır", "actionUpdateUser": "Kullanıcıyı Güncelle", "actionGetUser": "Kullanıcıyı Getir", "actionGetOrgUser": "Kuruluş Kullanıcısını Al", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Sunucu konsolundan kurulum simgesini girin.", "setupTokenRequired": "Kurulum simgesi gerekli", "actionUpdateSite": "Siteyi Güncelle", + "actionApproveSite": "Siteyi Onayla", + "actionRejectSite": "Siteyi Reddet", "actionResetSiteBandwidth": "Organizasyon Bant Genişliğini Sıfırla", "actionListSiteRoles": "İzin Verilen Site Rolleri Listele", "actionCreateResource": "Kaynak Oluştur", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.", "otpAuthSubmit": "Kodu Gönder", "idpContinue": "Veya devam et:", + "idpLastUsed": "Son Kullanılan", "otpAuthBack": "Şifreye Geri Dön", "navbar": "Navigasyon Menüsü", "navbarDescription": "Uygulamanın ana navigasyon menüsü", From d7d31b78f033e030b5a060cbc02877864bebd2fa Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:15 -0400 Subject: [PATCH 42/94] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 3d34fef2e..e5a680d99 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -449,8 +449,14 @@ "provisioningManage": "预配", "provisioningDescription": "管理预配密钥,并审核待批准的站点。", "pendingSites": "待审批站点", - "siteApproveSuccess": "站点批准成功", + "siteApproveSuccess": "站点及相关资源已成功审批", "siteApproveError": "批准站点出错", + "siteReject": "拒绝站点", + "siteQuestionReject": "您确定要拒绝此站点吗?", + "siteMessageReject": "这将永久删除站点及所有尚未处理的相关资源。", + "siteConfirmReject": "确认拒绝站点", + "siteRejectSuccess": "站点已被成功拒绝", + "siteRejectError": "拒绝站点时出错", "provisioningKeys": "置备键", "searchProvisioningKeys": "搜索配备密钥...", "provisioningKeysAdd": "生成预配密钥", @@ -466,8 +472,8 @@ "provisioningKeysSave": "保存预配键", "provisioningKeysSaveDescription": "您只能看到一次。复制它到一个安全的地方。", "provisioningKeysErrorCreate": "创建预配键时出错", - "provisioningKeysList": "新建预配键", - "provisioningKeysMaxBatchSize": "最大批量大小", + "provisioningKeysList": "新预配密钥", + "provisioningKeysMaxBatchSize": "最大批次大小", "provisioningKeysUnlimitedBatchSize": "无限批量大小(无限制)", "provisioningKeysMaxBatchUnlimited": "无限制", "provisioningKeysMaxBatchSizeInvalid": "输入一个有效的最大批处理大小(1-1,000,000)。", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "永不过期", "provisioningKeysEdit": "编辑置备键", "provisioningKeysEditDescription": "更新此密钥的最大批量大小和过期时间。", - "provisioningKeysApproveNewSites": "批准新节点", + "provisioningKeysApproveNewSites": "批准新站点", "provisioningKeysApproveNewSitesDescription": "自动批准使用此密钥注册的节点。", "provisioningKeysUpdateError": "更新预配键时出错", "provisioningKeysUpdated": "置备密钥已更新", @@ -1397,6 +1403,7 @@ "createOrgUser": "创建组织用户", "actionUpdateOrg": "更新组织", "actionRemoveInvitation": "移除邀请", + "actionRemoveUserRole": "移除用户角色", "actionUpdateUser": "更新用户", "actionGetUser": "获取用户", "actionGetOrgUser": "获取组织用户", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "从服务器控制台输入设置令牌。", "setupTokenRequired": "需要设置令牌", "actionUpdateSite": "更新站点", + "actionApproveSite": "批准站点", + "actionRejectSite": "拒绝站点", "actionResetSiteBandwidth": "重置组织带宽", "actionListSiteRoles": "允许站点角色列表", "actionCreateResource": "创建资源", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。", "otpAuthSubmit": "提交代码", "idpContinue": "或者继续", + "idpLastUsed": "上次使用", "otpAuthBack": "回到密码", "navbar": "导航菜单", "navbarDescription": "应用程序的主导航菜单", From 4d9e38d022f7b33cb009bd3a31de35ecc7e31d3a Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Wed, 15 Jul 2026 20:54:17 -0400 Subject: [PATCH 43/94] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 1e0d6a1a3..9bcca74db 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -449,8 +449,14 @@ "provisioningManage": "Levering", "provisioningDescription": "Administrer foreløpig nøkler og gjennomgå ventende nettsteder som venter på godkjenning.", "pendingSites": "Ventende nettsteder", - "siteApproveSuccess": "Vellykket godkjenning av nettsted", + "siteApproveSuccess": "Område og tilknyttede ressurser godkjent", "siteApproveError": "Feil ved godkjenning av side", + "siteReject": "Avvis Område", + "siteQuestionReject": "Er du sikker på at du vil avvise dette området?", + "siteMessageReject": "Dette vil permanent slette området og eventuelle tilknyttede ressurser som fortsatt venter.", + "siteConfirmReject": "Bekreft Avvisning av Område", + "siteRejectSuccess": "Område avvist", + "siteRejectError": "Feil ved avvisning av området", "provisioningKeys": "Foreløpig nøkler", "searchProvisioningKeys": "Søk varer i lagrings nøkler...", "provisioningKeysAdd": "Generer fremvisende nøkkel", @@ -466,12 +472,12 @@ "provisioningKeysSave": "Lagre den midlertidig nøkkelen", "provisioningKeysSaveDescription": "Du kan bare se denne én gang. Kopier det til et sikkert sted.", "provisioningKeysErrorCreate": "Feil under oppretting av foreløpig nøkkel", - "provisioningKeysList": "Ny provisorisk nøkkel", - "provisioningKeysMaxBatchSize": "Maks størrelse på bunt", + "provisioningKeysList": "Ny Forsyningsnøkkel", + "provisioningKeysMaxBatchSize": "Maks Batch Størrelse", "provisioningKeysUnlimitedBatchSize": "Ubegrenset mengde bunt (ingen begrensning)", "provisioningKeysMaxBatchUnlimited": "Ubegrenset", "provisioningKeysMaxBatchSizeInvalid": "Angi en gyldig sjakkstørrelse (1–1 000.000).", - "provisioningKeysValidUntil": "Gyldig til", + "provisioningKeysValidUntil": "Gyldig Til", "provisioningKeysValidUntilHint": "La stå tomt for ingen utløp.", "provisioningKeysValidUntilInvalid": "Angi en gyldig dato og klokkeslett.", "provisioningKeysNumUsed": "Antall ganger brukt", @@ -480,7 +486,7 @@ "provisioningKeysNeverUsed": "Aldri", "provisioningKeysEdit": "Rediger bestemmelsesnøkkel", "provisioningKeysEditDescription": "Oppdater maksimal størrelse for bunt og utløpstid for denne nøkkelen.", - "provisioningKeysApproveNewSites": "Godkjenn nye nettsteder", + "provisioningKeysApproveNewSites": "Godkjenn Nye Områder", "provisioningKeysApproveNewSitesDescription": "Godkjenn automatisk nettsteder som registrerer deg med denne nøkkelen.", "provisioningKeysUpdateError": "Feil under oppdatering av foreløpig nøkkel", "provisioningKeysUpdated": "Foreslå nøkkel oppdatert", @@ -1397,6 +1403,7 @@ "createOrgUser": "Opprett Org bruker", "actionUpdateOrg": "Oppdater organisasjon", "actionRemoveInvitation": "Fjern invitasjon", + "actionRemoveUserRole": "Fjern Brukerrolle", "actionUpdateUser": "Oppdater bruker", "actionGetUser": "Hent bruker", "actionGetOrgUser": "Hent organisasjonsbruker", @@ -1419,6 +1426,8 @@ "setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.", "setupTokenRequired": "Oppsetttoken er nødvendig", "actionUpdateSite": "Oppdater område", + "actionApproveSite": "Godkjenn Område", + "actionRejectSite": "Avvis Område", "actionResetSiteBandwidth": "Tilbakestill organisasjons-båndbredde", "actionListSiteRoles": "List opp tillatte områderoller", "actionCreateResource": "Opprett ressurs", @@ -1516,6 +1525,7 @@ "otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.", "otpAuthSubmit": "Send inn kode", "idpContinue": "Eller fortsett med", + "idpLastUsed": "Sist brukt", "otpAuthBack": "Tilbake til passord", "navbar": "Navigasjonsmeny", "navbarDescription": "Hovednavigasjonsmeny for applikasjonen", From 3fb36c143430e6ab905aa040866e30bf16d93eba Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 15 Jul 2026 22:03:28 -0400 Subject: [PATCH 44/94] add missing translation --- messages/en-US.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index 23195eab0..6893b41fa 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -3811,5 +3811,6 @@ "rdpUploadFailed": "Upload failed", "rdpUnicodeKeyboardMode": "Unicode keyboard mode", "sessionToolbarShow": "Show toolbar", - "sessionToolbarHide": "Hide toolbar" + "sessionToolbarHide": "Hide toolbar", + "actionUpdateSiteApprovals": "Update Site Approvals" } From 530a1a53509fe9392f2b1a382eb04f1fbf769ae2 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 17:30:15 -0400 Subject: [PATCH 45/94] Add new column --- server/db/pg/schema/schema.ts | 1 + server/db/sqlite/schema/schema.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index f3375b0d9..12f291604 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -107,6 +107,7 @@ export const sites = pgTable( lastPing: integer("lastPing"), address: varchar("address"), endpoint: varchar("endpoint"), + localEndpoints: varchar("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to publicKey: varchar("publicKey"), lastHolePunch: bigint("lastHolePunch", { mode: "number" }), listenPort: integer("listenPort"), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 3fcf38a41..b8a01af89 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -118,6 +118,7 @@ export const sites = sqliteTable("sites", { // exit node stuff that is how to connect to the site when it has a wg server address: text("address"), // this is the address of the wireguard interface in newt endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config + localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to publicKey: text("publicKey"), // TODO: Fix typo in publicKey lastHolePunch: integer("lastHolePunch"), listenPort: integer("listenPort"), From 7f9c7603807ce9fd5864d6b5e037700ceacb39fa Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 20:45:18 -0400 Subject: [PATCH 46/94] Save the local endpoints --- server/routers/newt/handleNewtRegisterMessage.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 0dc8380c8..bb77fb53f 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -48,7 +48,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pingResults, newtVersion, backwardsCompatible, - chainId + chainId, + localEndpoints } = message.data; if (!publicKey) { logger.warn("Public key not provided"); @@ -131,7 +132,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .set({ pubKey: publicKey, exitNodeId: exitNodeId, - subnet: newSubnet + subnet: newSubnet, + localEndpoints: localEndpoints || null }) .where(eq(sites.siteId, siteId)) .returning(); @@ -139,7 +141,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { await db .update(sites) .set({ - pubKey: publicKey + pubKey: publicKey, + localEndpoints: localEndpoints || null }) .where(eq(sites.siteId, siteId)) .returning(); From 903e8c0fa10f7488cb32a6e2edcdae244a881f17 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 10:47:24 -0400 Subject: [PATCH 47/94] Send localEndpoints on sites to olm --- .../routers/newt/handleNewtRegisterMessage.ts | 8 +++-- server/routers/olm/buildConfiguration.ts | 4 +++ .../olm/handleOlmServerPeerAddMessage.ts | 32 ++++++------------- server/routers/olm/peers.ts | 2 ++ 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index bb77fb53f..17a8d955a 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -133,7 +133,9 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pubKey: publicKey, exitNodeId: exitNodeId, subnet: newSubnet, - localEndpoints: localEndpoints || null + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); @@ -142,7 +144,9 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .update(sites) .set({ pubKey: publicKey, - localEndpoints: localEndpoints || null + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); diff --git a/server/routers/olm/buildConfiguration.ts b/server/routers/olm/buildConfiguration.ts index 0266fa041..37355eae5 100644 --- a/server/routers/olm/buildConfiguration.ts +++ b/server/routers/olm/buildConfiguration.ts @@ -30,6 +30,7 @@ export async function buildSiteConfigurationForOlmClient( siteId: number; name?: string; endpoint?: string; + localEndpoints?: string[]; publicKey?: string; serverIP?: string | null; serverPort?: number | null; @@ -206,6 +207,9 @@ export async function buildSiteConfigurationForOlmClient( name: site.name, // relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing endpoint: site.endpoint, + localEndpoints: site.localEndpoints + ? JSON.parse(site.localEndpoints) + : undefined, publicKey: site.publicKey, serverIP: site.address, serverPort: site.listenPort, diff --git a/server/routers/olm/handleOlmServerPeerAddMessage.ts b/server/routers/olm/handleOlmServerPeerAddMessage.ts index 5f46ea84c..332afe082 100644 --- a/server/routers/olm/handleOlmServerPeerAddMessage.ts +++ b/server/routers/olm/handleOlmServerPeerAddMessage.ts @@ -3,24 +3,15 @@ import { db, networks, siteNetworks, - siteResources, + siteResources } from "@server/db"; import { MessageHandler } from "@server/routers/ws"; -import { - clients, - clientSitesAssociationsCache, - Olm, - sites -} from "@server/db"; +import { clients, clientSitesAssociationsCache, Olm, sites } from "@server/db"; import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm"; import logger from "@server/logger"; -import { - generateAliasConfig, -} from "@server/lib/ip"; +import { generateAliasConfig } from "@server/lib/ip"; import { generateRemoteSubnets } from "@server/lib/ip"; -import { - addPeer as newtAddPeer, -} from "@server/routers/newt/peers"; +import { addPeer as newtAddPeer } from "@server/routers/newt/peers"; export const handleOlmServerPeerAddMessage: MessageHandler = async ( context @@ -135,10 +126,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( clientSiteResourcesAssociationsCache.siteResourceId ) ) - .innerJoin( - networks, - eq(siteResources.networkId, networks.networkId) - ) + .innerJoin(networks, eq(siteResources.networkId, networks.networkId)) .innerJoin( siteNetworks, and( @@ -147,10 +135,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( ) ) .where( - eq( - clientSiteResourcesAssociationsCache.clientId, - client.clientId - ) + eq(clientSiteResourcesAssociationsCache.clientId, client.clientId) ); // Return connect message with all site configurations @@ -161,6 +146,9 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( siteId: site.siteId, name: site.name, endpoint: site.endpoint, + localEndpoints: site.localEndpoints + ? JSON.parse(site.localEndpoints) + : undefined, publicKey: site.publicKey, serverIP: site.address, serverPort: site.listenPort, @@ -170,7 +158,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( aliases: generateAliasConfig( allSiteResources.map(({ siteResources }) => siteResources) ), - chainId: chainId, + chainId: chainId } }, broadcast: false, diff --git a/server/routers/olm/peers.ts b/server/routers/olm/peers.ts index 962d7367e..960c6ad23 100644 --- a/server/routers/olm/peers.ts +++ b/server/routers/olm/peers.ts @@ -18,6 +18,7 @@ export async function addPeer( serverPort: number | null; remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access aliases: Alias[]; + localEndpoints?: string[]; // optional, list of local endpoints for the peer }, olmId?: string, version?: string | null @@ -44,6 +45,7 @@ export async function addPeer( name: peer.name, publicKey: peer.publicKey, endpoint: peer.endpoint, + localEndpoints: peer.localEndpoints, relayEndpoint: peer.relayEndpoint, serverIP: peer.serverIP, serverPort: peer.serverPort, From 515afc14a5695fa21a70ff8ff1364c90adacecd0 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 11:23:17 -0400 Subject: [PATCH 48/94] Add handlers --- server/routers/olm/handleOlmLocalMessage.ts | 74 +++++++++++++++ server/routers/olm/handleOlmRelayMessage.ts | 4 +- server/routers/olm/handleOlmUnLocalMessage.ts | 95 +++++++++++++++++++ server/routers/olm/handleOlmUnRelayMessage.ts | 4 +- server/routers/olm/index.ts | 2 + server/routers/ws/messageHandlers.ts | 6 +- 6 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 server/routers/olm/handleOlmLocalMessage.ts create mode 100644 server/routers/olm/handleOlmUnLocalMessage.ts diff --git a/server/routers/olm/handleOlmLocalMessage.ts b/server/routers/olm/handleOlmLocalMessage.ts new file mode 100644 index 000000000..83ef8cbf1 --- /dev/null +++ b/server/routers/olm/handleOlmLocalMessage.ts @@ -0,0 +1,74 @@ +import { db, sites } from "@server/db"; +import { MessageHandler } from "@server/routers/ws"; +import { clients, Olm } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import { updatePeer as newtUpdatePeer } from "../newt/peers"; +import logger from "@server/logger"; + +export const handleOlmLocalMessage: MessageHandler = async (context) => { + const { message, client: c, sendToClient } = context; + const olm = c as Olm; + + logger.info("Handling local olm message!"); + + if (!olm) { + logger.warn("Olm not found"); + return; + } + + if (!olm.clientId) { + logger.warn("Olm has no client!"); + return; + } + + const clientId = olm.clientId; + + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + logger.warn("Client not found"); + return; + } + + // make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old + if (!client.pubKey) { + logger.warn("Client has no endpoint or listen port"); + return; + } + + const { siteId, chainId } = message.data; + + // Get the site + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!site || !site.exitNodeId) { + logger.warn("Site not found or has no exit node"); + return; + } + + // update the peer on the newt + await newtUpdatePeer(siteId, client.pubKey, { + endpoint: "" // this removes the endpoint so the newt knows to accept local + }); + + // Just ack the message, we don't keep sending it + return { + message: { + type: "olm/wg/peer/local", + data: { + siteId: siteId, + chainId + } + }, + broadcast: false, + excludeSender: false + }; +}; diff --git a/server/routers/olm/handleOlmRelayMessage.ts b/server/routers/olm/handleOlmRelayMessage.ts index 7196824d2..406ed7bbb 100644 --- a/server/routers/olm/handleOlmRelayMessage.ts +++ b/server/routers/olm/handleOlmRelayMessage.ts @@ -79,9 +79,9 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => { ) ); - // update the peer on the exit node + // update the peer on the newt await newtUpdatePeer(siteId, client.pubKey, { - endpoint: "" // this removes the endpoint so the exit node knows to relay + endpoint: "" // this removes the endpoint so the newt knows to relay }); return { diff --git a/server/routers/olm/handleOlmUnLocalMessage.ts b/server/routers/olm/handleOlmUnLocalMessage.ts new file mode 100644 index 000000000..55d312815 --- /dev/null +++ b/server/routers/olm/handleOlmUnLocalMessage.ts @@ -0,0 +1,95 @@ +import { db, exitNodes, sites } from "@server/db"; +import { MessageHandler } from "@server/routers/ws"; +import { clients, clientSitesAssociationsCache, Olm } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import { updatePeer as newtUpdatePeer } from "../newt/peers"; +import logger from "@server/logger"; + +export const handleOlmUnLocalMessage: MessageHandler = async (context) => { + const { message, client: c, sendToClient } = context; + const olm = c as Olm; + + logger.info("Handling unlocal olm message!"); + + if (!olm) { + logger.warn("Olm not found"); + return; + } + + if (!olm.clientId) { + logger.warn("Olm has no client!"); + return; + } + + const clientId = olm.clientId; + + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + logger.warn("Client not found"); + return; + } + + // make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old + if (!client.pubKey) { + logger.warn("Client has no endpoint or listen port"); + return; + } + + const { siteId, chainId } = message.data; + + // Get the site + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!site) { + logger.warn("Site not found or has no exit node"); + return; + } + + const [clientSiteAssociation] = await db + .select() + .from(clientSitesAssociationsCache) + .where( + and( + eq(clientSitesAssociationsCache.clientId, olm.clientId), + eq(clientSitesAssociationsCache.siteId, siteId) + ) + ); + + if (!clientSiteAssociation) { + logger.warn("Client-Site association not found"); + return; + } + + if (!clientSiteAssociation.endpoint) { + logger.warn("Client-Site association has no endpoint, cannot unrelay"); + return; + } + + // update the peer on the newt + await newtUpdatePeer(siteId, client.pubKey, { + endpoint: clientSiteAssociation.isRelayed + ? "" + : clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt + }); + + return { + message: { + type: "olm/wg/peer/unlocal", + data: { + siteId: siteId, + chainId + } + }, + broadcast: false, + excludeSender: false + }; +}; diff --git a/server/routers/olm/handleOlmUnRelayMessage.ts b/server/routers/olm/handleOlmUnRelayMessage.ts index a7b426023..3f73a5834 100644 --- a/server/routers/olm/handleOlmUnRelayMessage.ts +++ b/server/routers/olm/handleOlmUnRelayMessage.ts @@ -77,9 +77,9 @@ export const handleOlmUnRelayMessage: MessageHandler = async (context) => { return; } - // update the peer on the exit node + // update the peer on the newt await newtUpdatePeer(siteId, client.pubKey, { - endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the exit node + endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt }); return { diff --git a/server/routers/olm/index.ts b/server/routers/olm/index.ts index 5c151a8cf..e11d4e48e 100644 --- a/server/routers/olm/index.ts +++ b/server/routers/olm/index.ts @@ -13,3 +13,5 @@ export * from "./recoverOlmWithFingerprint"; export * from "./handleOlmDisconnectingMessage"; export * from "./handleOlmServerInitAddPeerHandshake"; export * from "./offlineChecker"; +export * from "./handleOlmUnLocalMessage"; +export * from "./handleOlmLocalMessage"; diff --git a/server/routers/ws/messageHandlers.ts b/server/routers/ws/messageHandlers.ts index f89284389..496002142 100644 --- a/server/routers/ws/messageHandlers.ts +++ b/server/routers/ws/messageHandlers.ts @@ -20,7 +20,9 @@ import { handleOlmServerPeerAddMessage, handleOlmUnRelayMessage, handleOlmDisconnectingMessage, - handleOlmServerInitAddPeerHandshake + handleOlmServerInitAddPeerHandshake, + handleOlmLocalMessage, + handleOlmUnLocalMessage } from "../olm"; import { handleHealthcheckStatusMessage } from "../target"; import { handleRoundTripMessage } from "./handleRoundTripMessage"; @@ -32,6 +34,8 @@ export const messageHandlers: Record = { "olm/wg/register": handleOlmRegisterMessage, "olm/wg/relay": handleOlmRelayMessage, "olm/wg/unrelay": handleOlmUnRelayMessage, + "olm/wg/local": handleOlmLocalMessage, + "olm/wg/unlocal": handleOlmUnLocalMessage, "olm/ping": handleOlmPingMessage, "olm/disconnecting": handleOlmDisconnectingMessage, "newt/disconnecting": handleNewtDisconnectingMessage, From dbfb8e83e8547bba1374ca385af8f3c8f1be766d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 14:43:45 -0400 Subject: [PATCH 49/94] Local endpoints from the get config --- server/routers/newt/handleNewtGetConfigMessage.ts | 7 +++++-- server/routers/newt/handleNewtRegisterMessage.ts | 13 +++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/server/routers/newt/handleNewtGetConfigMessage.ts b/server/routers/newt/handleNewtGetConfigMessage.ts index fd5e2b42e..d99dd89ef 100644 --- a/server/routers/newt/handleNewtGetConfigMessage.ts +++ b/server/routers/newt/handleNewtGetConfigMessage.ts @@ -29,7 +29,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => { return; } - const { publicKey, port, chainId } = message.data; + const { publicKey, port, localEndpoints, chainId } = message.data; const siteId = newt.siteId; // Get the current site data @@ -69,7 +69,10 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => { .update(sites) .set({ publicKey, - listenPort: port + listenPort: port, + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 17a8d955a..0dc8380c8 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -48,8 +48,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pingResults, newtVersion, backwardsCompatible, - chainId, - localEndpoints + chainId } = message.data; if (!publicKey) { logger.warn("Public key not provided"); @@ -132,10 +131,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .set({ pubKey: publicKey, exitNodeId: exitNodeId, - subnet: newSubnet, - localEndpoints: localEndpoints - ? JSON.stringify(localEndpoints) - : null + subnet: newSubnet }) .where(eq(sites.siteId, siteId)) .returning(); @@ -143,10 +139,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { await db .update(sites) .set({ - pubKey: publicKey, - localEndpoints: localEndpoints - ? JSON.stringify(localEndpoints) - : null + pubKey: publicKey }) .where(eq(sites.siteId, siteId)) .returning(); From 55f3807491f5f84a6656d800fd1d4c811d40f878 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 15:10:38 -0400 Subject: [PATCH 50/94] Enforce uniqueness of aliases in blueprints --- server/lib/blueprints/privateResources.ts | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/server/lib/blueprints/privateResources.ts b/server/lib/blueprints/privateResources.ts index 44901e8c8..f3fa7bf5d 100644 --- a/server/lib/blueprints/privateResources.ts +++ b/server/lib/blueprints/privateResources.ts @@ -239,6 +239,31 @@ export async function updatePrivateResources( ); } + if (resourceData.alias) { + const [aliasConflict] = await trx + .select({ + siteResourceId: siteResources.siteResourceId + }) + .from(siteResources) + .where( + and( + eq(siteResources.orgId, orgId), + eq(siteResources.alias, resourceData.alias), + ne( + siteResources.siteResourceId, + existingResource.siteResourceId + ) + ) + ) + .limit(1); + + if (aliasConflict) { + throw new Error( + `Alias ${resourceData.alias} already in use by another site resource in org ${orgId}` + ); + } + } + // Update existing resource const [updatedResource] = await trx .update(siteResources) @@ -480,6 +505,27 @@ export async function updatePrivateResources( ); } + if (resourceData.alias) { + const [aliasConflict] = await trx + .select({ + siteResourceId: siteResources.siteResourceId + }) + .from(siteResources) + .where( + and( + eq(siteResources.orgId, orgId), + eq(siteResources.alias, resourceData.alias) + ) + ) + .limit(1); + + if (aliasConflict) { + throw new Error( + `Alias ${resourceData.alias} already in use by another site resource in org ${orgId}` + ); + } + } + const [network] = await trx .insert(networks) .values({ From a88b79e066c9271675ece2d390cb678c243a9bf6 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 16:09:35 -0400 Subject: [PATCH 51/94] Add timezone offset to fix bad display --- server/lib/statusHistory.ts | 67 ++++++++++++++----- .../routers/healthChecks/getStatusHistory.ts | 9 ++- server/routers/resource/getStatusHistory.ts | 9 ++- server/routers/site/getStatusHistory.ts | 9 ++- src/lib/queries.ts | 17 +++-- 5 files changed, 82 insertions(+), 29 deletions(-) diff --git a/server/lib/statusHistory.ts b/server/lib/statusHistory.ts index 7c5b5c370..d11ea88ef 100644 --- a/server/lib/statusHistory.ts +++ b/server/lib/statusHistory.ts @@ -8,26 +8,42 @@ const STATUS_HISTORY_CACHE_TTL = 60; // seconds function statusHistoryCacheKey( entityType: string, entityId: number, - days: number + days: number, + tzOffsetMinutes: number ): string { - return `statusHistory:${entityType}:${entityId}:${days}`; + return `statusHistory:${entityType}:${entityId}:${days}:${tzOffsetMinutes}`; +} + +// Returns the epoch seconds of the most recent local-calendar-day midnight, +// where "local" is defined by tzOffsetMinutes (minutes to ADD to UTC to get +// local time, e.g. Australia/Sydney standard time is 600). Defaults to 0 +// (UTC) so callers that don't pass a timezone keep the original behavior. +function localMidnightSec(tzOffsetMinutes: number): number { + const localNow = new Date(Date.now() + tzOffsetMinutes * 60_000); + localNow.setUTCHours(0, 0, 0, 0); + return Math.floor(localNow.getTime() / 1000) - tzOffsetMinutes * 60; } export async function getCachedStatusHistory( entityType: string, entityId: number, - days: number + days: number, + tzOffsetMinutes: number = 0 ): Promise { - const cacheKey = statusHistoryCacheKey(entityType, entityId, days); + const cacheKey = statusHistoryCacheKey( + entityType, + entityId, + days, + tzOffsetMinutes + ); const cached = await cache.get(cacheKey); if (cached !== undefined) { return cached; } - // Anchor to UTC midnight so the query window aligns with stable calendar days - const utcToday = new Date(); - utcToday.setUTCHours(0, 0, 0, 0); - const todayMidnightSec = Math.floor(utcToday.getTime() / 1000); + // Anchor to local midnight (UTC when tzOffsetMinutes is 0) so the query + // window aligns with stable calendar days for the requesting client + const todayMidnightSec = localMidnightSec(tzOffsetMinutes); const startSec = todayMidnightSec - days * 86400; const events = await logsDb @@ -63,7 +79,8 @@ export async function getCachedStatusHistory( const { buckets, totalDowntime } = computeBuckets( events, days, - priorStatus + priorStatus, + tzOffsetMinutes ); const totalWindow = days * 86400; const overallUptime = @@ -99,11 +116,19 @@ export const statusHistoryQuerySchema = z days: z .string() .optional() - .transform((v) => (v ? parseInt(v, 10) : 90)) + .transform((v) => (v ? parseInt(v, 10) : 90)), + // Minutes to add to UTC to get the requesting client's local time + // (e.g. Australia/Sydney standard time is 600). Optional and + // defaults to 0 (UTC) so older clients keep the prior behavior. + tzOffsetMinutes: z + .string() + .optional() + .transform((v) => (v ? parseInt(v, 10) : 0)) }) .pipe( z.object({ - days: z.number().int().min(1).max(365) + days: z.number().int().min(1).max(365), + tzOffsetMinutes: z.number().int().min(-720).max(840) }) ); @@ -133,15 +158,15 @@ export function computeBuckets( id: number; }[], days: number, - priorStatus: string | null = null + priorStatus: string | null = null, + tzOffsetMinutes: number = 0 ): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } { const nowSec = Math.floor(Date.now() / 1000); - // Anchor bucket boundaries to UTC midnight so dates are stable calendar days - // and don't drift as the cache expires and is recomputed - const utcToday = new Date(); - utcToday.setUTCHours(0, 0, 0, 0); - const todayMidnightSec = Math.floor(utcToday.getTime() / 1000); + // Anchor bucket boundaries to local midnight (UTC when tzOffsetMinutes is + // 0) so dates are stable calendar days for the requesting client and + // don't drift as the cache expires and is recomputed + const todayMidnightSec = localMidnightSec(tzOffsetMinutes); const buckets: StatusHistoryDayBucket[] = []; let totalDowntime = 0; @@ -237,7 +262,13 @@ export function computeBuckets( ) : 100; - const dateStr = new Date(dayStartSec * 1000).toISOString().slice(0, 10); + // Shift by the client's offset before formatting so the label reflects + // their local calendar date rather than the UTC date of dayStartSec + const dateStr = new Date( + (dayStartSec + tzOffsetMinutes * 60) * 1000 + ) + .toISOString() + .slice(0, 10); const hasAnyData = currentStatus !== null || dayEvents.length > 0; diff --git a/server/private/routers/healthChecks/getStatusHistory.ts b/server/private/routers/healthChecks/getStatusHistory.ts index 51a59e2e1..484870886 100644 --- a/server/private/routers/healthChecks/getStatusHistory.ts +++ b/server/private/routers/healthChecks/getStatusHistory.ts @@ -55,9 +55,14 @@ export async function getHealthCheckStatusHistory( const entityType = "health_check"; const entityId = parsedParams.data.healthCheckId; - const { days } = parsedQuery.data; + const { days, tzOffsetMinutes } = parsedQuery.data; - const data = await getCachedStatusHistory(entityType, entityId, days); + const data = await getCachedStatusHistory( + entityType, + entityId, + days, + tzOffsetMinutes + ); return response(res, { data, diff --git a/server/routers/resource/getStatusHistory.ts b/server/routers/resource/getStatusHistory.ts index c3dcf6c88..19e11b78f 100644 --- a/server/routers/resource/getStatusHistory.ts +++ b/server/routers/resource/getStatusHistory.ts @@ -42,9 +42,14 @@ export async function getResourceStatusHistory( const entityType = "resource"; const entityId = parsedParams.data.resourceId; - const { days } = parsedQuery.data; + const { days, tzOffsetMinutes } = parsedQuery.data; - const data = await getCachedStatusHistory(entityType, entityId, days); + const data = await getCachedStatusHistory( + entityType, + entityId, + days, + tzOffsetMinutes + ); return response(res, { data, diff --git a/server/routers/site/getStatusHistory.ts b/server/routers/site/getStatusHistory.ts index 26f1dbbd2..d281961e9 100644 --- a/server/routers/site/getStatusHistory.ts +++ b/server/routers/site/getStatusHistory.ts @@ -42,9 +42,14 @@ export async function getSiteStatusHistory( const entityType = "site"; const entityId = parsedParams.data.siteId; - const { days } = parsedQuery.data; + const { days, tzOffsetMinutes } = parsedQuery.data; - const data = await getCachedStatusHistory(entityType, entityId, days); + const data = await getCachedStatusHistory( + entityType, + entityId, + days, + tzOffsetMinutes + ); return response(res, { data, diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 8fdb1c531..3194e5651 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -650,9 +650,13 @@ export const orgQueries = { queryOptions({ queryKey: ["SITE_STATUS_HISTORY", siteId, days] as const, queryFn: async ({ signal, meta }) => { + const tzOffsetMinutes = -new Date().getTimezoneOffset(); const res = await meta!.api.get< AxiosResponse - >(`/site/${siteId}/status-history?days=${days}`, { signal }); + >( + `/site/${siteId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`, + { signal } + ); return res.data.data; } }), @@ -667,11 +671,13 @@ export const orgQueries = { queryOptions({ queryKey: ["RESOURCE_STATUS_HISTORY", resourceId, days] as const, queryFn: async ({ signal, meta }) => { + const tzOffsetMinutes = -new Date().getTimezoneOffset(); const res = await meta!.api.get< AxiosResponse - >(`/resource/${resourceId}/status-history?days=${days}`, { - signal - }); + >( + `/resource/${resourceId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`, + { signal } + ); return res.data.data; } }), @@ -693,10 +699,11 @@ export const orgQueries = { days ] as const, queryFn: async ({ signal, meta }) => { + const tzOffsetMinutes = -new Date().getTimezoneOffset(); const res = await meta!.api.get< AxiosResponse >( - `/org/${orgId}/health-check/${healthCheckId}/status-history?days=${days}`, + `/org/${orgId}/health-check/${healthCheckId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`, { signal } ); return res.data.data; From 08c5ec2be78673a5b3d231383c513028f03f5f61 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 17:30:15 -0400 Subject: [PATCH 52/94] Add new column --- server/db/pg/schema/schema.ts | 1 + server/db/sqlite/schema/schema.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index f3375b0d9..12f291604 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -107,6 +107,7 @@ export const sites = pgTable( lastPing: integer("lastPing"), address: varchar("address"), endpoint: varchar("endpoint"), + localEndpoints: varchar("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to publicKey: varchar("publicKey"), lastHolePunch: bigint("lastHolePunch", { mode: "number" }), listenPort: integer("listenPort"), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index 3fcf38a41..b8a01af89 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -118,6 +118,7 @@ export const sites = sqliteTable("sites", { // exit node stuff that is how to connect to the site when it has a wg server address: text("address"), // this is the address of the wireguard interface in newt endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config + localEndpoints: text("localEndpoints"), // JSON encoded list of string ips on the local machine to try to connect to publicKey: text("publicKey"), // TODO: Fix typo in publicKey lastHolePunch: integer("lastHolePunch"), listenPort: integer("listenPort"), From 3ac7ef23ae9d136bbaf132850c3af84286001693 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 20:45:18 -0400 Subject: [PATCH 53/94] Save the local endpoints --- server/routers/newt/handleNewtRegisterMessage.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 0dc8380c8..bb77fb53f 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -48,7 +48,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pingResults, newtVersion, backwardsCompatible, - chainId + chainId, + localEndpoints } = message.data; if (!publicKey) { logger.warn("Public key not provided"); @@ -131,7 +132,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .set({ pubKey: publicKey, exitNodeId: exitNodeId, - subnet: newSubnet + subnet: newSubnet, + localEndpoints: localEndpoints || null }) .where(eq(sites.siteId, siteId)) .returning(); @@ -139,7 +141,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { await db .update(sites) .set({ - pubKey: publicKey + pubKey: publicKey, + localEndpoints: localEndpoints || null }) .where(eq(sites.siteId, siteId)) .returning(); From d5eb67a8fc6e26dd5e6de90ab50045bb25840a77 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 10:47:24 -0400 Subject: [PATCH 54/94] Send localEndpoints on sites to olm --- .../routers/newt/handleNewtRegisterMessage.ts | 8 +++-- server/routers/olm/buildConfiguration.ts | 4 +++ .../olm/handleOlmServerPeerAddMessage.ts | 32 ++++++------------- server/routers/olm/peers.ts | 2 ++ 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index bb77fb53f..17a8d955a 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -133,7 +133,9 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pubKey: publicKey, exitNodeId: exitNodeId, subnet: newSubnet, - localEndpoints: localEndpoints || null + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); @@ -142,7 +144,9 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .update(sites) .set({ pubKey: publicKey, - localEndpoints: localEndpoints || null + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); diff --git a/server/routers/olm/buildConfiguration.ts b/server/routers/olm/buildConfiguration.ts index 0266fa041..37355eae5 100644 --- a/server/routers/olm/buildConfiguration.ts +++ b/server/routers/olm/buildConfiguration.ts @@ -30,6 +30,7 @@ export async function buildSiteConfigurationForOlmClient( siteId: number; name?: string; endpoint?: string; + localEndpoints?: string[]; publicKey?: string; serverIP?: string | null; serverPort?: number | null; @@ -206,6 +207,9 @@ export async function buildSiteConfigurationForOlmClient( name: site.name, // relayEndpoint: relayEndpoint, // this can be undefined now if not relayed // lets not do this for now because it would conflict with the hole punch testing endpoint: site.endpoint, + localEndpoints: site.localEndpoints + ? JSON.parse(site.localEndpoints) + : undefined, publicKey: site.publicKey, serverIP: site.address, serverPort: site.listenPort, diff --git a/server/routers/olm/handleOlmServerPeerAddMessage.ts b/server/routers/olm/handleOlmServerPeerAddMessage.ts index 5f46ea84c..332afe082 100644 --- a/server/routers/olm/handleOlmServerPeerAddMessage.ts +++ b/server/routers/olm/handleOlmServerPeerAddMessage.ts @@ -3,24 +3,15 @@ import { db, networks, siteNetworks, - siteResources, + siteResources } from "@server/db"; import { MessageHandler } from "@server/routers/ws"; -import { - clients, - clientSitesAssociationsCache, - Olm, - sites -} from "@server/db"; +import { clients, clientSitesAssociationsCache, Olm, sites } from "@server/db"; import { and, eq, inArray, isNotNull, isNull } from "drizzle-orm"; import logger from "@server/logger"; -import { - generateAliasConfig, -} from "@server/lib/ip"; +import { generateAliasConfig } from "@server/lib/ip"; import { generateRemoteSubnets } from "@server/lib/ip"; -import { - addPeer as newtAddPeer, -} from "@server/routers/newt/peers"; +import { addPeer as newtAddPeer } from "@server/routers/newt/peers"; export const handleOlmServerPeerAddMessage: MessageHandler = async ( context @@ -135,10 +126,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( clientSiteResourcesAssociationsCache.siteResourceId ) ) - .innerJoin( - networks, - eq(siteResources.networkId, networks.networkId) - ) + .innerJoin(networks, eq(siteResources.networkId, networks.networkId)) .innerJoin( siteNetworks, and( @@ -147,10 +135,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( ) ) .where( - eq( - clientSiteResourcesAssociationsCache.clientId, - client.clientId - ) + eq(clientSiteResourcesAssociationsCache.clientId, client.clientId) ); // Return connect message with all site configurations @@ -161,6 +146,9 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( siteId: site.siteId, name: site.name, endpoint: site.endpoint, + localEndpoints: site.localEndpoints + ? JSON.parse(site.localEndpoints) + : undefined, publicKey: site.publicKey, serverIP: site.address, serverPort: site.listenPort, @@ -170,7 +158,7 @@ export const handleOlmServerPeerAddMessage: MessageHandler = async ( aliases: generateAliasConfig( allSiteResources.map(({ siteResources }) => siteResources) ), - chainId: chainId, + chainId: chainId } }, broadcast: false, diff --git a/server/routers/olm/peers.ts b/server/routers/olm/peers.ts index 962d7367e..960c6ad23 100644 --- a/server/routers/olm/peers.ts +++ b/server/routers/olm/peers.ts @@ -18,6 +18,7 @@ export async function addPeer( serverPort: number | null; remoteSubnets: string[] | null; // optional, comma-separated list of subnets that this site can access aliases: Alias[]; + localEndpoints?: string[]; // optional, list of local endpoints for the peer }, olmId?: string, version?: string | null @@ -44,6 +45,7 @@ export async function addPeer( name: peer.name, publicKey: peer.publicKey, endpoint: peer.endpoint, + localEndpoints: peer.localEndpoints, relayEndpoint: peer.relayEndpoint, serverIP: peer.serverIP, serverPort: peer.serverPort, From 3a616cc80481443b9e8f244547edb32fe313746f Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 11:23:17 -0400 Subject: [PATCH 55/94] Add handlers --- server/routers/olm/handleOlmLocalMessage.ts | 74 +++++++++++++++ server/routers/olm/handleOlmRelayMessage.ts | 4 +- server/routers/olm/handleOlmUnLocalMessage.ts | 95 +++++++++++++++++++ server/routers/olm/handleOlmUnRelayMessage.ts | 4 +- server/routers/olm/index.ts | 2 + server/routers/ws/messageHandlers.ts | 6 +- 6 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 server/routers/olm/handleOlmLocalMessage.ts create mode 100644 server/routers/olm/handleOlmUnLocalMessage.ts diff --git a/server/routers/olm/handleOlmLocalMessage.ts b/server/routers/olm/handleOlmLocalMessage.ts new file mode 100644 index 000000000..83ef8cbf1 --- /dev/null +++ b/server/routers/olm/handleOlmLocalMessage.ts @@ -0,0 +1,74 @@ +import { db, sites } from "@server/db"; +import { MessageHandler } from "@server/routers/ws"; +import { clients, Olm } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import { updatePeer as newtUpdatePeer } from "../newt/peers"; +import logger from "@server/logger"; + +export const handleOlmLocalMessage: MessageHandler = async (context) => { + const { message, client: c, sendToClient } = context; + const olm = c as Olm; + + logger.info("Handling local olm message!"); + + if (!olm) { + logger.warn("Olm not found"); + return; + } + + if (!olm.clientId) { + logger.warn("Olm has no client!"); + return; + } + + const clientId = olm.clientId; + + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + logger.warn("Client not found"); + return; + } + + // make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old + if (!client.pubKey) { + logger.warn("Client has no endpoint or listen port"); + return; + } + + const { siteId, chainId } = message.data; + + // Get the site + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!site || !site.exitNodeId) { + logger.warn("Site not found or has no exit node"); + return; + } + + // update the peer on the newt + await newtUpdatePeer(siteId, client.pubKey, { + endpoint: "" // this removes the endpoint so the newt knows to accept local + }); + + // Just ack the message, we don't keep sending it + return { + message: { + type: "olm/wg/peer/local", + data: { + siteId: siteId, + chainId + } + }, + broadcast: false, + excludeSender: false + }; +}; diff --git a/server/routers/olm/handleOlmRelayMessage.ts b/server/routers/olm/handleOlmRelayMessage.ts index 7196824d2..406ed7bbb 100644 --- a/server/routers/olm/handleOlmRelayMessage.ts +++ b/server/routers/olm/handleOlmRelayMessage.ts @@ -79,9 +79,9 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => { ) ); - // update the peer on the exit node + // update the peer on the newt await newtUpdatePeer(siteId, client.pubKey, { - endpoint: "" // this removes the endpoint so the exit node knows to relay + endpoint: "" // this removes the endpoint so the newt knows to relay }); return { diff --git a/server/routers/olm/handleOlmUnLocalMessage.ts b/server/routers/olm/handleOlmUnLocalMessage.ts new file mode 100644 index 000000000..55d312815 --- /dev/null +++ b/server/routers/olm/handleOlmUnLocalMessage.ts @@ -0,0 +1,95 @@ +import { db, exitNodes, sites } from "@server/db"; +import { MessageHandler } from "@server/routers/ws"; +import { clients, clientSitesAssociationsCache, Olm } from "@server/db"; +import { and, eq } from "drizzle-orm"; +import { updatePeer as newtUpdatePeer } from "../newt/peers"; +import logger from "@server/logger"; + +export const handleOlmUnLocalMessage: MessageHandler = async (context) => { + const { message, client: c, sendToClient } = context; + const olm = c as Olm; + + logger.info("Handling unlocal olm message!"); + + if (!olm) { + logger.warn("Olm not found"); + return; + } + + if (!olm.clientId) { + logger.warn("Olm has no client!"); + return; + } + + const clientId = olm.clientId; + + const [client] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + + if (!client) { + logger.warn("Client not found"); + return; + } + + // make sure we hand endpoints for both the site and the client and the lastHolePunch is not too old + if (!client.pubKey) { + logger.warn("Client has no endpoint or listen port"); + return; + } + + const { siteId, chainId } = message.data; + + // Get the site + const [site] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (!site) { + logger.warn("Site not found or has no exit node"); + return; + } + + const [clientSiteAssociation] = await db + .select() + .from(clientSitesAssociationsCache) + .where( + and( + eq(clientSitesAssociationsCache.clientId, olm.clientId), + eq(clientSitesAssociationsCache.siteId, siteId) + ) + ); + + if (!clientSiteAssociation) { + logger.warn("Client-Site association not found"); + return; + } + + if (!clientSiteAssociation.endpoint) { + logger.warn("Client-Site association has no endpoint, cannot unrelay"); + return; + } + + // update the peer on the newt + await newtUpdatePeer(siteId, client.pubKey, { + endpoint: clientSiteAssociation.isRelayed + ? "" + : clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt + }); + + return { + message: { + type: "olm/wg/peer/unlocal", + data: { + siteId: siteId, + chainId + } + }, + broadcast: false, + excludeSender: false + }; +}; diff --git a/server/routers/olm/handleOlmUnRelayMessage.ts b/server/routers/olm/handleOlmUnRelayMessage.ts index a7b426023..3f73a5834 100644 --- a/server/routers/olm/handleOlmUnRelayMessage.ts +++ b/server/routers/olm/handleOlmUnRelayMessage.ts @@ -77,9 +77,9 @@ export const handleOlmUnRelayMessage: MessageHandler = async (context) => { return; } - // update the peer on the exit node + // update the peer on the newt await newtUpdatePeer(siteId, client.pubKey, { - endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the exit node + endpoint: clientSiteAssociation.endpoint // this is the endpoint of the client to connect directly to the newt }); return { diff --git a/server/routers/olm/index.ts b/server/routers/olm/index.ts index 5c151a8cf..e11d4e48e 100644 --- a/server/routers/olm/index.ts +++ b/server/routers/olm/index.ts @@ -13,3 +13,5 @@ export * from "./recoverOlmWithFingerprint"; export * from "./handleOlmDisconnectingMessage"; export * from "./handleOlmServerInitAddPeerHandshake"; export * from "./offlineChecker"; +export * from "./handleOlmUnLocalMessage"; +export * from "./handleOlmLocalMessage"; diff --git a/server/routers/ws/messageHandlers.ts b/server/routers/ws/messageHandlers.ts index f89284389..496002142 100644 --- a/server/routers/ws/messageHandlers.ts +++ b/server/routers/ws/messageHandlers.ts @@ -20,7 +20,9 @@ import { handleOlmServerPeerAddMessage, handleOlmUnRelayMessage, handleOlmDisconnectingMessage, - handleOlmServerInitAddPeerHandshake + handleOlmServerInitAddPeerHandshake, + handleOlmLocalMessage, + handleOlmUnLocalMessage } from "../olm"; import { handleHealthcheckStatusMessage } from "../target"; import { handleRoundTripMessage } from "./handleRoundTripMessage"; @@ -32,6 +34,8 @@ export const messageHandlers: Record = { "olm/wg/register": handleOlmRegisterMessage, "olm/wg/relay": handleOlmRelayMessage, "olm/wg/unrelay": handleOlmUnRelayMessage, + "olm/wg/local": handleOlmLocalMessage, + "olm/wg/unlocal": handleOlmUnLocalMessage, "olm/ping": handleOlmPingMessage, "olm/disconnecting": handleOlmDisconnectingMessage, "newt/disconnecting": handleNewtDisconnectingMessage, From 6135f2b72713407d4a7ac4471ad7d5ce662e0746 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 14:43:45 -0400 Subject: [PATCH 56/94] Local endpoints from the get config --- server/routers/newt/handleNewtGetConfigMessage.ts | 7 +++++-- server/routers/newt/handleNewtRegisterMessage.ts | 13 +++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/server/routers/newt/handleNewtGetConfigMessage.ts b/server/routers/newt/handleNewtGetConfigMessage.ts index fd5e2b42e..d99dd89ef 100644 --- a/server/routers/newt/handleNewtGetConfigMessage.ts +++ b/server/routers/newt/handleNewtGetConfigMessage.ts @@ -29,7 +29,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => { return; } - const { publicKey, port, chainId } = message.data; + const { publicKey, port, localEndpoints, chainId } = message.data; const siteId = newt.siteId; // Get the current site data @@ -69,7 +69,10 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => { .update(sites) .set({ publicKey, - listenPort: port + listenPort: port, + localEndpoints: localEndpoints + ? JSON.stringify(localEndpoints) + : null }) .where(eq(sites.siteId, siteId)) .returning(); diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 17a8d955a..0dc8380c8 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -48,8 +48,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { pingResults, newtVersion, backwardsCompatible, - chainId, - localEndpoints + chainId } = message.data; if (!publicKey) { logger.warn("Public key not provided"); @@ -132,10 +131,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { .set({ pubKey: publicKey, exitNodeId: exitNodeId, - subnet: newSubnet, - localEndpoints: localEndpoints - ? JSON.stringify(localEndpoints) - : null + subnet: newSubnet }) .where(eq(sites.siteId, siteId)) .returning(); @@ -143,10 +139,7 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { await db .update(sites) .set({ - pubKey: publicKey, - localEndpoints: localEndpoints - ? JSON.stringify(localEndpoints) - : null + pubKey: publicKey }) .where(eq(sites.siteId, siteId)) .returning(); From 3c9f0946d2df6db8199fb654fd1b2c50a1839464 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 16 Jul 2026 17:56:31 -0400 Subject: [PATCH 57/94] add site provisioning keys to integration api --- messages/en-US.json | 4 + server/middlewares/integration/index.ts | 1 + .../verifyApiKeySiteProvisioningKeyAccess.ts | 111 ++++++++++++++++++ server/openApi.ts | 1 + server/private/routers/integration.ts | 44 +++++++ .../createSiteProvisioningKey.ts | 60 +++++++++- .../deleteSiteProvisioningKey.ts | 33 +++++- .../listSiteProvisioningKeys.ts | 56 +++++++-- .../updateSiteProvisioningKey.ts | 61 ++++++++-- src/components/PermissionsSelectBox.tsx | 7 ++ 10 files changed, 355 insertions(+), 23 deletions(-) create mode 100644 server/middlewares/integration/verifyApiKeySiteProvisioningKeyAccess.ts diff --git a/messages/en-US.json b/messages/en-US.json index 6893b41fa..678c73ebd 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "List Invitations", "actionExportLogs": "Export Logs", "actionViewLogs": "View Logs", + "actionCreateSiteProvisioningKey": "Create Site Provisioning Key", + "actionListSiteProvisioningKeys": "List Site Provisioning Keys", + "actionUpdateSiteProvisioningKey": "Update Site Provisioning Key", + "actionDeleteSiteProvisioningKey": "Delete Site Provisioning Key", "noneSelected": "None selected", "orgNotFound2": "No organizations found.", "search": "Search…", diff --git a/server/middlewares/integration/index.ts b/server/middlewares/integration/index.ts index d43854eab..be9d400cf 100644 --- a/server/middlewares/integration/index.ts +++ b/server/middlewares/integration/index.ts @@ -17,3 +17,4 @@ export * from "./verifyApiKeySiteResourceAccess"; export * from "./verifyApiKeyIdpAccess"; export * from "./verifyApiKeyDomainAccess"; export * from "./verifyApiKeyResourcePolicyAccess"; +export * from "./verifyApiKeySiteProvisioningKeyAccess"; diff --git a/server/middlewares/integration/verifyApiKeySiteProvisioningKeyAccess.ts b/server/middlewares/integration/verifyApiKeySiteProvisioningKeyAccess.ts new file mode 100644 index 000000000..d1982ad3c --- /dev/null +++ b/server/middlewares/integration/verifyApiKeySiteProvisioningKeyAccess.ts @@ -0,0 +1,111 @@ +import { Request, Response, NextFunction } from "express"; +import { + db, + siteProvisioningKeys, + siteProvisioningKeyOrg, + apiKeyOrg +} from "@server/db"; +import { and, eq } from "drizzle-orm"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; +import { getFirstString } from "@server/lib/requestParams"; + +export async function verifyApiKeySiteProvisioningKeyAccess( + req: Request, + res: Response, + next: NextFunction +) { + try { + const apiKey = req.apiKey; + const siteProvisioningKeyId = + getFirstString(req.params.siteProvisioningKeyId) || + getFirstString(req.body.siteProvisioningKeyId) || + getFirstString(req.query.siteProvisioningKeyId); + const orgId = getFirstString(req.params.orgId); + + if (!apiKey) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated") + ); + } + + if (!orgId) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID") + ); + } + + if (!siteProvisioningKeyId) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid key ID") + ); + } + + if (apiKey.isRoot) { + // Root keys can access any site provisioning key in any org + return next(); + } + + const [row] = await db + .select() + .from(siteProvisioningKeys) + .innerJoin( + siteProvisioningKeyOrg, + and( + eq( + siteProvisioningKeys.siteProvisioningKeyId, + siteProvisioningKeyOrg.siteProvisioningKeyId + ), + eq(siteProvisioningKeyOrg.orgId, orgId) + ) + ) + .where( + eq( + siteProvisioningKeys.siteProvisioningKeyId, + siteProvisioningKeyId + ) + ) + .limit(1); + + if (!row?.siteProvisioningKeys || !row.siteProvisioningKeyOrg) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site provisioning key with ID ${siteProvisioningKeyId} not found for organization ${orgId}` + ) + ); + } + + if (!req.apiKeyOrg) { + const apiKeyOrgRes = await db + .select() + .from(apiKeyOrg) + .where( + and( + eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId), + eq(apiKeyOrg.orgId, row.siteProvisioningKeyOrg.orgId) + ) + ) + .limit(1); + req.apiKeyOrg = apiKeyOrgRes[0]; + } + + if (!req.apiKeyOrg) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Key does not have access to this organization" + ) + ); + } + + return next(); + } catch (error) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Error verifying site provisioning key access" + ) + ); + } +} diff --git a/server/openApi.ts b/server/openApi.ts index 7132a0327..63625192d 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -18,6 +18,7 @@ export enum OpenAPITags { OrgIdp = "Identity Provider (Organization Only)", Client = "Client", ApiKey = "API Key", + SiteProvisioningKey = "Site Provisioning Key", Domain = "Domain", Blueprint = "Blueprint", Ssh = "SSH", diff --git a/server/private/routers/integration.ts b/server/private/routers/integration.ts index 820a843f0..b613b2f27 100644 --- a/server/private/routers/integration.ts +++ b/server/private/routers/integration.ts @@ -16,6 +16,7 @@ import * as org from "#private/routers/org"; import * as logs from "#private/routers/auditLogs"; import * as alertEvents from "#private/routers/alertEvents"; import * as certificates from "#private/routers/certificates"; +import * as siteProvisioning from "#private/routers/siteProvisioning"; import { verifyApiKeyHasAction, @@ -24,6 +25,7 @@ import { verifyApiKeyIdpAccess, verifyApiKeyRoleAccess, verifyApiKeyUserAccess, + verifyApiKeySiteProvisioningKeyAccess, verifyLimits } from "@server/middlewares"; import * as user from "#private/routers/user"; @@ -215,3 +217,45 @@ authenticated.delete( logActionAudit(ActionsEnum.removeUserRole), user.removeUserRole ); + +authenticated.put( + "/org/:orgId/site-provisioning-key", + verifyValidLicense, + verifyValidSubscription(tierMatrix.siteProvisioningKeys), + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createSiteProvisioningKey), + logActionAudit(ActionsEnum.createSiteProvisioningKey), + siteProvisioning.createSiteProvisioningKey +); + +authenticated.get( + "/org/:orgId/site-provisioning-keys", + verifyValidLicense, + verifyValidSubscription(tierMatrix.siteProvisioningKeys), + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listSiteProvisioningKeys), + siteProvisioning.listSiteProvisioningKeys +); + +authenticated.delete( + "/org/:orgId/site-provisioning-key/:siteProvisioningKeyId", + verifyValidLicense, + verifyValidSubscription(tierMatrix.siteProvisioningKeys), + verifyApiKeyOrgAccess, + verifyApiKeySiteProvisioningKeyAccess, + verifyApiKeyHasAction(ActionsEnum.deleteSiteProvisioningKey), + logActionAudit(ActionsEnum.deleteSiteProvisioningKey), + siteProvisioning.deleteSiteProvisioningKey +); + +authenticated.patch( + "/org/:orgId/site-provisioning-key/:siteProvisioningKeyId", + verifyValidLicense, + verifyValidSubscription(tierMatrix.siteProvisioningKeys), + verifyApiKeyOrgAccess, + verifyApiKeySiteProvisioningKeyAccess, + verifyApiKeyHasAction(ActionsEnum.updateSiteProvisioningKey), + logActionAudit(ActionsEnum.updateSiteProvisioningKey), + siteProvisioning.updateSiteProvisioningKey +); diff --git a/server/private/routers/siteProvisioning/createSiteProvisioningKey.ts b/server/private/routers/siteProvisioning/createSiteProvisioningKey.ts index f5b64c0f3..b810ebe4f 100644 --- a/server/private/routers/siteProvisioning/createSiteProvisioningKey.ts +++ b/server/private/routers/siteProvisioning/createSiteProvisioningKey.ts @@ -26,6 +26,8 @@ import { import logger from "@server/logger"; import { hashPassword } from "@server/auth/password"; import type { CreateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types"; +import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema"; +import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.object({ orgId: z.string().nonempty() @@ -34,10 +36,17 @@ const paramsSchema = z.object({ const bodySchema = z .strictObject({ name: z.string().min(1).max(255), - maxBatchSize: z.union([ - z.null(), - z.coerce.number().int().positive().max(1_000_000) - ]), + maxBatchSize: z + .union([ + z.null(), + z.coerce.number().int().positive().max(1_000_000) + ]) + .openapi({ + type: "number", + default: null, + description: + "Maximum number of sites that can be provisioned in a single batch. If null, there is no limit." + }), validUntil: z.string().max(255).optional(), approveNewSites: z.boolean().optional().default(true) }) @@ -57,6 +66,49 @@ const bodySchema = z export type CreateSiteProvisioningKeyBody = z.infer; +const CreateSiteProvisioningKeyResponseDataSchema = z.object({ + siteProvisioningKeyId: z.string(), + orgId: z.string(), + name: z.string(), + siteProvisioningKey: z.string(), + lastChars: z.string(), + createdAt: z.string(), + lastUsed: z.string().nullable(), + maxBatchSize: z.number().nullable(), + numUsed: z.number(), + validUntil: z.string().nullable(), + approveNewSites: z.boolean() +}); + +registry.registerPath({ + method: "put", + path: "/org/{orgId}/site-provisioning-key", + description: "Create a new site provisioning key for the organization.", + tags: [OpenAPITags.SiteProvisioningKey], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: bodySchema + } + } + } + }, + responses: { + 201: { + description: "Successful response", + content: { + "application/json": { + schema: createApiResponseSchema( + CreateSiteProvisioningKeyResponseDataSchema + ) + } + } + } + } +}); + export async function createSiteProvisioningKey( req: Request, res: Response, diff --git a/server/private/routers/siteProvisioning/deleteSiteProvisioningKey.ts b/server/private/routers/siteProvisioning/deleteSiteProvisioningKey.ts index 61fa0a850..9c77c93ca 100644 --- a/server/private/routers/siteProvisioning/deleteSiteProvisioningKey.ts +++ b/server/private/routers/siteProvisioning/deleteSiteProvisioningKey.ts @@ -13,23 +13,46 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { - db, - siteProvisioningKeyOrg, - siteProvisioningKeys -} from "@server/db"; +import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db"; import { and, eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.object({ siteProvisioningKeyId: z.string().nonempty(), orgId: z.string().nonempty() }); +registry.registerPath({ + method: "delete", + path: "/org/{orgId}/site-provisioning-key/{siteProvisioningKeyId}", + description: "Delete a site provisioning key.", + tags: [OpenAPITags.SiteProvisioningKey], + request: { + params: paramsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function deleteSiteProvisioningKey( req: Request, res: Response, diff --git a/server/private/routers/siteProvisioning/listSiteProvisioningKeys.ts b/server/private/routers/siteProvisioning/listSiteProvisioningKeys.ts index 0e9838b24..63ab4429a 100644 --- a/server/private/routers/siteProvisioning/listSiteProvisioningKeys.ts +++ b/server/private/routers/siteProvisioning/listSiteProvisioningKeys.ts @@ -11,11 +11,7 @@ * This file is not licensed under the AGPLv3. */ -import { - db, - siteProvisioningKeyOrg, - siteProvisioningKeys -} from "@server/db"; +import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; @@ -25,6 +21,8 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; import { eq } from "drizzle-orm"; import type { ListSiteProvisioningKeysResponse } from "@server/routers/siteProvisioning/types"; +import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema"; +import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.object({ orgId: z.string().nonempty() @@ -45,11 +43,55 @@ const querySchema = z.object({ .pipe(z.int().nonnegative()) }); +const ListSiteProvisioningKeysResponseDataSchema = z.object({ + siteProvisioningKeys: z.array( + z.object({ + siteProvisioningKeyId: z.string(), + orgId: z.string(), + lastChars: z.string(), + createdAt: z.string(), + name: z.string(), + lastUsed: z.string().nullable(), + maxBatchSize: z.number().nullable(), + numUsed: z.number(), + validUntil: z.string().nullable(), + approveNewSites: z.boolean() + }) + ), + pagination: z.object({ + total: z.number(), + limit: z.number(), + offset: z.number() + }) +}); + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/site-provisioning-keys", + description: "List all site provisioning keys for an organization.", + tags: [OpenAPITags.SiteProvisioningKey], + request: { + params: paramsSchema, + query: querySchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: createApiResponseSchema( + ListSiteProvisioningKeysResponseDataSchema + ) + } + } + } + } +}); + function querySiteProvisioningKeys(orgId: string) { return db .select({ - siteProvisioningKeyId: - siteProvisioningKeys.siteProvisioningKeyId, + siteProvisioningKeyId: siteProvisioningKeys.siteProvisioningKeyId, orgId: siteProvisioningKeyOrg.orgId, lastChars: siteProvisioningKeys.lastChars, createdAt: siteProvisioningKeys.createdAt, diff --git a/server/private/routers/siteProvisioning/updateSiteProvisioningKey.ts b/server/private/routers/siteProvisioning/updateSiteProvisioningKey.ts index cee40212a..f97389b2b 100644 --- a/server/private/routers/siteProvisioning/updateSiteProvisioningKey.ts +++ b/server/private/routers/siteProvisioning/updateSiteProvisioningKey.ts @@ -13,11 +13,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { - db, - siteProvisioningKeyOrg, - siteProvisioningKeys -} from "@server/db"; +import { db, siteProvisioningKeyOrg, siteProvisioningKeys } from "@server/db"; import { and, eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -25,6 +21,8 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import type { UpdateSiteProvisioningKeyResponse } from "@server/routers/siteProvisioning/types"; +import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema"; +import { OpenAPITags, registry } from "@server/openApi"; const paramsSchema = z.object({ siteProvisioningKeyId: z.string().nonempty(), @@ -38,7 +36,13 @@ const bodySchema = z z.null(), z.coerce.number().int().positive().max(1_000_000) ]) - .optional(), + .optional() + .openapi({ + type: "number", + default: null, + description: + "Maximum number of sites that can be provisioned in a single batch. If null, there is no limit." + }), validUntil: z.string().max(255).optional(), approveNewSites: z.boolean().optional() }) @@ -50,7 +54,8 @@ const bodySchema = z ) { ctx.addIssue({ code: "custom", - message: "Provide maxBatchSize and/or validUntil and/or approveNewSites", + message: + "Provide maxBatchSize and/or validUntil and/or approveNewSites", path: ["maxBatchSize"] }); } @@ -69,6 +74,48 @@ const bodySchema = z export type UpdateSiteProvisioningKeyBody = z.infer; +const UpdateSiteProvisioningKeyResponseDataSchema = z.object({ + siteProvisioningKeyId: z.string(), + orgId: z.string(), + name: z.string(), + lastChars: z.string(), + createdAt: z.string(), + lastUsed: z.string().nullable(), + maxBatchSize: z.number().nullable(), + numUsed: z.number(), + validUntil: z.string().nullable(), + approveNewSites: z.boolean() +}); + +registry.registerPath({ + method: "patch", + path: "/org/{orgId}/site-provisioning-key/{siteProvisioningKeyId}", + description: "Update a site provisioning key.", + tags: [OpenAPITags.SiteProvisioningKey], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: bodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: createApiResponseSchema( + UpdateSiteProvisioningKeyResponseDataSchema + ) + } + } + } + } +}); + export async function updateSiteProvisioningKey( req: Request, res: Response, diff --git a/src/components/PermissionsSelectBox.tsx b/src/components/PermissionsSelectBox.tsx index 48a492c31..48868722e 100644 --- a/src/components/PermissionsSelectBox.tsx +++ b/src/components/PermissionsSelectBox.tsx @@ -143,6 +143,13 @@ function getActionsCategories(root: boolean) { Logs: { [t("actionExportLogs")]: "exportLogs", [t("actionViewLogs")]: "viewLogs" + }, + + "Site Provisioning Key": { + [t("actionCreateSiteProvisioningKey")]: "createSiteProvisioningKey", + [t("actionListSiteProvisioningKeys")]: "listSiteProvisioningKeys", + [t("actionUpdateSiteProvisioningKey")]: "updateSiteProvisioningKey", + [t("actionDeleteSiteProvisioningKey")]: "deleteSiteProvisioningKey" } }; From a30119f5e875604e050105fcb37a8aeb787e1d58 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 16 Jul 2026 21:28:36 -0400 Subject: [PATCH 58/94] encode email in url param --- server/routers/auth/requestPasswordReset.ts | 2 +- .../user/adminGeneratePasswordResetCode.ts | 2 +- server/routers/user/inviteUser.ts | 4 +-- src/components/InviteStatusCard.tsx | 28 +++++++++++++------ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index 42b53d24d..aca435c7b 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -99,7 +99,7 @@ export async function requestPasswordReset( }); }); - const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${email}&token=${token}`; + const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${encodeURIComponent(email)}&token=${token}`; if (!config.getRawConfig().email) { logger.info( diff --git a/server/routers/user/adminGeneratePasswordResetCode.ts b/server/routers/user/adminGeneratePasswordResetCode.ts index 562a459e1..7ba8ce9df 100644 --- a/server/routers/user/adminGeneratePasswordResetCode.ts +++ b/server/routers/user/adminGeneratePasswordResetCode.ts @@ -94,7 +94,7 @@ export async function adminGeneratePasswordResetCode( }); }); - const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${existingUser[0].email}&token=${token}`; + const url = `${config.getRawConfig().app.dashboard_url}/auth/reset-password?email=${encodeURIComponent(existingUser[0].email!)}&token=${token}`; logger.info( `Admin generated password reset code for user ${existingUser[0].email} (${userId})` diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index 7445ee910..7b7ae9250 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -287,7 +287,7 @@ export async function inviteUser( ) ); - const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`; + const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; if (doEmail) { await sendEmail( @@ -341,7 +341,7 @@ export async function inviteUser( .values(uniqueRoleIds.map((roleId) => ({ inviteId, roleId }))); }); - const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${email}`; + const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; if (doEmail) { await sendEmail( diff --git a/src/components/InviteStatusCard.tsx b/src/components/InviteStatusCard.tsx index 40b7123e7..790249904 100644 --- a/src/components/InviteStatusCard.tsx +++ b/src/components/InviteStatusCard.tsx @@ -93,14 +93,20 @@ export default function InviteStatusCard({ setType(type); if (!user && type === "user_does_not_exist") { + const inviteRedirect = encodeURIComponent( + `/invite?token=${tokenParam}` + ); const redirectUrl = email - ? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}` - : `/auth/signup?redirect=/invite?token=${tokenParam}`; + ? `/auth/signup?redirect=${inviteRedirect}&email=${encodeURIComponent(email)}` + : `/auth/signup?redirect=${inviteRedirect}`; router.push(redirectUrl); } else if (!user && type === "not_logged_in") { + const inviteRedirect = encodeURIComponent( + `/invite?token=${tokenParam}` + ); const redirectUrl = email - ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}` - : `/auth/login?redirect=/invite?token=${tokenParam}`; + ? `/auth/login?redirect=${inviteRedirect}&user=${encodeURIComponent(email)}` + : `/auth/login?redirect=${inviteRedirect}`; router.push(redirectUrl); } else { setLoading(false); @@ -112,17 +118,23 @@ export default function InviteStatusCard({ async function goToLogin() { await api.post("/auth/logout", {}); + const inviteRedirect = encodeURIComponent( + `/invite?token=${tokenParam}` + ); const redirectUrl = email - ? `/auth/login?redirect=/invite?token=${tokenParam}&user=${email}` - : `/auth/login?redirect=/invite?token=${tokenParam}`; + ? `/auth/login?redirect=${inviteRedirect}&user=${encodeURIComponent(email)}` + : `/auth/login?redirect=${inviteRedirect}`; router.push(redirectUrl); } async function goToSignup() { await api.post("/auth/logout", {}); + const inviteRedirect = encodeURIComponent( + `/invite?token=${tokenParam}` + ); const redirectUrl = email - ? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${email}` - : `/auth/signup?redirect=/invite?token=${tokenParam}`; + ? `/auth/signup?redirect=${inviteRedirect}&email=${encodeURIComponent(email)}` + : `/auth/signup?redirect=${inviteRedirect}`; router.push(redirectUrl); } From 9e50735800d6535f4b97eeb9432af89f91834005 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:29 -0400 Subject: [PATCH 59/94] New translations en-us.json (French) [ci skip] --- messages/fr-FR.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/fr-FR.json b/messages/fr-FR.json index ec24ec4d1..5a931fa70 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Lister les invitations", "actionExportLogs": "Exporter les journaux", "actionViewLogs": "Voir les logs", + "actionCreateSiteProvisioningKey": "Créer une clé de provisionnement de site", + "actionListSiteProvisioningKeys": "Lister les clés de provisionnement de site", + "actionUpdateSiteProvisioningKey": "Mettre à jour la clé de provisionnement de site", + "actionDeleteSiteProvisioningKey": "Supprimer la clé de provisionnement de site", "noneSelected": "Aucune sélection", "orgNotFound2": "Aucune organisation trouvée.", "search": "Rechercher…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Échec du téléchargement", "rdpUnicodeKeyboardMode": "Mode clavier Unicode", "sessionToolbarShow": "Afficher la barre d'outils", - "sessionToolbarHide": "Masquer la barre d'outils" + "sessionToolbarHide": "Masquer la barre d'outils", + "actionUpdateSiteApprovals": "Mettre à jour les approbations de site" } From 6df804ab3247c86667a0af8e4ae9c302a175f01e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:31 -0400 Subject: [PATCH 60/94] New translations en-us.json (Spanish) [ci skip] --- messages/es-ES.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/es-ES.json b/messages/es-ES.json index d7453592a..e677252a0 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Listar invitaciones", "actionExportLogs": "Exportar registros", "actionViewLogs": "Ver registros", + "actionCreateSiteProvisioningKey": "Crear clave de aprovisionamiento del sitio", + "actionListSiteProvisioningKeys": "Listado de claves de aprovisionamiento del sitio", + "actionUpdateSiteProvisioningKey": "Actualizar clave de aprovisionamiento del sitio", + "actionDeleteSiteProvisioningKey": "Eliminar clave de aprovisionamiento del sitio", "noneSelected": "Ninguno seleccionado", "orgNotFound2": "No se encontraron organizaciones.", "search": "Buscar…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Error de subida", "rdpUnicodeKeyboardMode": "Modo teclado Unicode", "sessionToolbarShow": "Mostrar barra de herramientas", - "sessionToolbarHide": "Ocultar barra de herramientas" + "sessionToolbarHide": "Ocultar barra de herramientas", + "actionUpdateSiteApprovals": "Actualizar aprobaciones del sitio" } From f9bcb25eaaa3ffc274647d4289850a7ce085cefa Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:33 -0400 Subject: [PATCH 61/94] New translations en-us.json (Bulgarian) [ci skip] --- messages/bg-BG.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 25e192fec..4bf0d9ab6 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Списък с покани", "actionExportLogs": "Експортиране на дневници", "actionViewLogs": "Преглед на дневници", + "actionCreateSiteProvisioningKey": "Създаване на ключ за предоставяне на сайт", + "actionListSiteProvisioningKeys": "Списък на ключовете за предоставяне на сайт", + "actionUpdateSiteProvisioningKey": "Актуализиране на ключ за предоставяне на сайт", + "actionDeleteSiteProvisioningKey": "Изтриване на ключ за предоставяне на сайт", "noneSelected": "Нищо не е избрано", "orgNotFound2": "Няма намерени организации.", "search": "Търси…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Качването неуспешно", "rdpUnicodeKeyboardMode": "Режим на unicode клавиатура", "sessionToolbarShow": "Показване на лентата с инструменти", - "sessionToolbarHide": "Скриване на лентата с инструменти" + "sessionToolbarHide": "Скриване на лентата с инструменти", + "actionUpdateSiteApprovals": "Обновяване на одобренията на сайта" } From 61a7dda29a287b8e1b0c787b6bdcec6bad42ba49 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:35 -0400 Subject: [PATCH 62/94] New translations en-us.json (Czech) [ci skip] --- messages/cs-CZ.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index f440a8e38..0d96a80cd 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Seznam pozvánek", "actionExportLogs": "Exportovat protokoly", "actionViewLogs": "Zobrazit logy", + "actionCreateSiteProvisioningKey": "Vytvořit klíč pro zřízení webu", + "actionListSiteProvisioningKeys": "Seznam klíčů pro zřízení webu", + "actionUpdateSiteProvisioningKey": "Aktualizovat klíč pro zřízení webu", + "actionDeleteSiteProvisioningKey": "Smazat klíč pro zřízení webu", "noneSelected": "Není vybráno", "orgNotFound2": "Nebyly nalezeny žádné organizace.", "search": "Vyhledávání…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Nahrání selhalo", "rdpUnicodeKeyboardMode": "Režim Unicode klávesnice", "sessionToolbarShow": "Zobrazit panel nástrojů", - "sessionToolbarHide": "Skrýt panel nástrojů" + "sessionToolbarHide": "Skrýt panel nástrojů", + "actionUpdateSiteApprovals": "Aktualizovat schválení webu" } From 7853f2b096e2fa7ced4761de9f17f4dd0aa57e0e Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:36 -0400 Subject: [PATCH 63/94] New translations en-us.json (Danish) [ci skip] --- messages/da-DK.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/da-DK.json b/messages/da-DK.json index 2e1376264..f88ffa474 100644 --- a/messages/da-DK.json +++ b/messages/da-DK.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Vis invitationer", "actionExportLogs": "Eksportér logs", "actionViewLogs": "Vis logs", + "actionCreateSiteProvisioningKey": "Opret provisioneringsnøgle til site", + "actionListSiteProvisioningKeys": "Vis provisioneringsnøgler til site", + "actionUpdateSiteProvisioningKey": "Opdater provisioneringsnøgle til site", + "actionDeleteSiteProvisioningKey": "Slet provisioneringsnøgle til site", "noneSelected": "Ingen valgt", "orgNotFound2": "Ingen organisationer fundet.", "search": "Søg…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Uploaden mislykkedes", "rdpUnicodeKeyboardMode": "Unicode tastaturtilstand", "sessionToolbarShow": "Vis værktøjslinje", - "sessionToolbarHide": "Skjul værktøjslinje" + "sessionToolbarHide": "Skjul værktøjslinje", + "actionUpdateSiteApprovals": "Opdater godkendelser på site" } From 672555f05130322d31d90de2d8e3ab3e5945b213 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:38 -0400 Subject: [PATCH 64/94] New translations en-us.json (German) [ci skip] --- messages/de-DE.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/de-DE.json b/messages/de-DE.json index 3ceac6dc4..615759bf1 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Einladungen auflisten", "actionExportLogs": "Logs exportieren", "actionViewLogs": "Logs anzeigen", + "actionCreateSiteProvisioningKey": "Bereitstellungsschlüssel für Standort erstellen", + "actionListSiteProvisioningKeys": "Liste der Bereitstellungsschlüssel für Standorte", + "actionUpdateSiteProvisioningKey": "Bereitstellungsschlüssel für Standort aktualisieren", + "actionDeleteSiteProvisioningKey": "Bereitstellungsschlüssel für Standort löschen", "noneSelected": "Keine ausgewählt", "orgNotFound2": "Keine Organisationen gefunden.", "search": "Suche…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Upload fehlgeschlagen", "rdpUnicodeKeyboardMode": "Unicode-Tastaturmodus", "sessionToolbarShow": "Werkzeugleiste zeigen", - "sessionToolbarHide": "Werkzeugleiste ausblenden" + "sessionToolbarHide": "Werkzeugleiste ausblenden", + "actionUpdateSiteApprovals": "Standortgenehmigungen aktualisieren" } From 714e3a8fb1af9bc4505713145318cf072fda9447 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:40 -0400 Subject: [PATCH 65/94] New translations en-us.json (Italian) [ci skip] --- messages/it-IT.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/it-IT.json b/messages/it-IT.json index 5edde2e2e..794eda30f 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Elenco Inviti", "actionExportLogs": "Esporta Log", "actionViewLogs": "Visualizza Log", + "actionCreateSiteProvisioningKey": "Crea Chiave di Provisioning del Sito", + "actionListSiteProvisioningKeys": "Elenca Chiavi di Provisioning del Sito", + "actionUpdateSiteProvisioningKey": "Aggiorna Chiave di Provisioning del Sito", + "actionDeleteSiteProvisioningKey": "Elimina Chiave di Provisioning del Sito", "noneSelected": "Nessuna selezione", "orgNotFound2": "Nessuna organizzazione trovata.", "search": "Cerca…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Caricamento fallito", "rdpUnicodeKeyboardMode": "Modalità tastiera Unicode", "sessionToolbarShow": "Mostra barra degli strumenti", - "sessionToolbarHide": "Nascondi barra degli strumenti" + "sessionToolbarHide": "Nascondi barra degli strumenti", + "actionUpdateSiteApprovals": "Aggiorna Approvazioni del Sito" } From 5f2ee77a73a6566b7ebde44f18f1dbba04e372c1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:41 -0400 Subject: [PATCH 66/94] New translations en-us.json (Korean) [ci skip] --- messages/ko-KR.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 67c0ee59f..8505e50e0 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "초대 목록", "actionExportLogs": "로그 내보내기", "actionViewLogs": "로그 보기", + "actionCreateSiteProvisioningKey": "사이트 프로비저닝 키 생성", + "actionListSiteProvisioningKeys": "사이트 프로비저닝 키 목록", + "actionUpdateSiteProvisioningKey": "사이트 프로비저닝 키 업데이트", + "actionDeleteSiteProvisioningKey": "사이트 프로비저닝 키 삭제", "noneSelected": "선택된 항목 없음", "orgNotFound2": "조직이 없습니다.", "search": "검색…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "업로드 실패", "rdpUnicodeKeyboardMode": "유니코드 키보드 모드", "sessionToolbarShow": "툴바 보기", - "sessionToolbarHide": "툴바 숨기기" + "sessionToolbarHide": "툴바 숨기기", + "actionUpdateSiteApprovals": "사이트 승인 업데이트" } From 86670c1d6089d7806a9e196fe9f604b7e8d4c956 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:43 -0400 Subject: [PATCH 67/94] New translations en-us.json (Dutch) [ci skip] --- messages/nl-NL.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 9239cd81c..f914a3c80 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Toon uitnodigingen", "actionExportLogs": "Logboeken exporteren", "actionViewLogs": "Logboeken bekijken", + "actionCreateSiteProvisioningKey": "Een sitevoorzie-ningssleutel aanmaken", + "actionListSiteProvisioningKeys": "Sitevoorzie-ningssleutels weergeven", + "actionUpdateSiteProvisioningKey": "Sitevoorzie-ningssleutel bijwerken", + "actionDeleteSiteProvisioningKey": "Sitevoorzie-ningssleutel verwijderen", "noneSelected": "Niet geselecteerd", "orgNotFound2": "Geen organisaties gevonden.", "search": "Zoeken…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Upload mislukt", "rdpUnicodeKeyboardMode": "Unicode toetsenbordmodus", "sessionToolbarShow": "Toon werkbalk", - "sessionToolbarHide": "Verberg werkbalk" + "sessionToolbarHide": "Verberg werkbalk", + "actionUpdateSiteApprovals": "Sitegoedkeuringen bijwerken" } From 3d9c413bbb70acc12cb3246a609889caf3edaea9 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:45 -0400 Subject: [PATCH 68/94] New translations en-us.json (Polish) [ci skip] --- messages/pl-PL.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 11df1b353..1e998dcdd 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Lista zaproszeń", "actionExportLogs": "Eksportuj dzienniki", "actionViewLogs": "Zobacz dzienniki", + "actionCreateSiteProvisioningKey": "Utwórz klucz konfiguracji strony", + "actionListSiteProvisioningKeys": "Lista kluczy konfiguracji strony", + "actionUpdateSiteProvisioningKey": "Zaktualizuj klucz konfiguracji strony", + "actionDeleteSiteProvisioningKey": "Usuń klucz konfiguracji strony", "noneSelected": "Nie wybrano", "orgNotFound2": "Nie znaleziono organizacji.", "search": "Szukaj…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Niepowodzenie przesyłania", "rdpUnicodeKeyboardMode": "Tryb klawiatury Unicode", "sessionToolbarShow": "Pokaż pasek narzędzi", - "sessionToolbarHide": "Ukryj pasek narzędzi" + "sessionToolbarHide": "Ukryj pasek narzędzi", + "actionUpdateSiteApprovals": "Zaktualizuj zgody na stronę" } From 8e09070bc1c2383b250b5bbace3b2e22482ea2c1 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:46 -0400 Subject: [PATCH 69/94] New translations en-us.json (Portuguese) [ci skip] --- messages/pt-PT.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 4a37fb52b..9d3dd1320 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Listar Convites", "actionExportLogs": "Exportar logs", "actionViewLogs": "Visualizar registros", + "actionCreateSiteProvisioningKey": "Criar Chave de Provisionamento do Site", + "actionListSiteProvisioningKeys": "Listar Chaves de Provisionamento do Site", + "actionUpdateSiteProvisioningKey": "Atualizar Chave de Provisionamento do Site", + "actionDeleteSiteProvisioningKey": "Excluir Chave de Provisionamento do Site", "noneSelected": "Nenhum selecionado", "orgNotFound2": "Nenhuma organização encontrada.", "search": "Pesquisar…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Falha no upload", "rdpUnicodeKeyboardMode": "Modo de teclado Unicode", "sessionToolbarShow": "Mostrar barra de ferramentas", - "sessionToolbarHide": "Ocultar barra de ferramentas" + "sessionToolbarHide": "Ocultar barra de ferramentas", + "actionUpdateSiteApprovals": "Atualizar Aprovações do Site" } From e5b9dfee661ce5b844165cb86cfe225c062ca07f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:48 -0400 Subject: [PATCH 70/94] New translations en-us.json (Russian) [ci skip] --- messages/ru-RU.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/ru-RU.json b/messages/ru-RU.json index efd5bee64..287e61497 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Список приглашений", "actionExportLogs": "Экспорт журналов", "actionViewLogs": "Просмотр журналов", + "actionCreateSiteProvisioningKey": "Создать ключ конфигурации сайта", + "actionListSiteProvisioningKeys": "Список ключей конфигурации сайтов", + "actionUpdateSiteProvisioningKey": "Обновить ключ конфигурации сайта", + "actionDeleteSiteProvisioningKey": "Удалить ключ конфигурации сайта", "noneSelected": "Ничего не выбрано", "orgNotFound2": "Организации не найдены.", "search": "Поиск…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Ошибка загрузки", "rdpUnicodeKeyboardMode": "Режим клавиатуры Unicode", "sessionToolbarShow": "Показать панель инструментов", - "sessionToolbarHide": "Скрыть панель инструментов" + "sessionToolbarHide": "Скрыть панель инструментов", + "actionUpdateSiteApprovals": "Обновить утверждения сайта" } From 50821e972d15c8711f6f0615b8df63e0fdd59654 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:50 -0400 Subject: [PATCH 71/94] New translations en-us.json (Turkish) [ci skip] --- messages/tr-TR.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 006180cc0..55f09d447 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Davetiyeleri Listele", "actionExportLogs": "Kayıtları Dışa Aktar", "actionViewLogs": "Kayıtları Görüntüle", + "actionCreateSiteProvisioningKey": "Site Sağlama Anahtarı Oluştur", + "actionListSiteProvisioningKeys": "Site Sağlama Anahtarlarını Listele", + "actionUpdateSiteProvisioningKey": "Site Sağlama Anahtarını Güncelle", + "actionDeleteSiteProvisioningKey": "Site Sağlama Anahtarını Sil", "noneSelected": "Hiçbiri seçili değil", "orgNotFound2": "Hiçbir organizasyon bulunamadı.", "search": "Ara…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Yükleme başarısız", "rdpUnicodeKeyboardMode": "Unicode klavye modu", "sessionToolbarShow": "Araç çubuğunu göster", - "sessionToolbarHide": "Araç çubuğunu gizle" + "sessionToolbarHide": "Araç çubuğunu gizle", + "actionUpdateSiteApprovals": "Site Onaylarını Güncelle" } From aa9de4d2acde5edbe61dc8e03efe6f84115211b4 Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:52 -0400 Subject: [PATCH 72/94] New translations en-us.json (Chinese Simplified) [ci skip] --- messages/zh-CN.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/zh-CN.json b/messages/zh-CN.json index e5a680d99..2f9b8805c 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "邀请列表", "actionExportLogs": "导出日志", "actionViewLogs": "查看日志", + "actionCreateSiteProvisioningKey": "创建站点预配密钥", + "actionListSiteProvisioningKeys": "列出站点预配密钥", + "actionUpdateSiteProvisioningKey": "更新站点预配密钥", + "actionDeleteSiteProvisioningKey": "删除站点预配密钥", "noneSelected": "未选择", "orgNotFound2": "未找到组织。", "search": "搜索…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "上传失败", "rdpUnicodeKeyboardMode": "Unicode 键盘模式", "sessionToolbarShow": "显示工具栏", - "sessionToolbarHide": "隐藏工具栏" + "sessionToolbarHide": "隐藏工具栏", + "actionUpdateSiteApprovals": "更新站点审批" } From a5808200f0840224c6fe14081767423cc5b2ae6f Mon Sep 17 00:00:00 2001 From: Owen Schwartz Date: Thu, 16 Jul 2026 21:34:54 -0400 Subject: [PATCH 73/94] New translations en-us.json (Norwegian Bokmal) [ci skip] --- messages/nb-NO.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/messages/nb-NO.json b/messages/nb-NO.json index 9bcca74db..8dd687d09 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -1511,6 +1511,10 @@ "actionListInvitations": "Liste invitasjoner", "actionExportLogs": "Eksportlogger", "actionViewLogs": "Vis logger", + "actionCreateSiteProvisioningKey": "Opprett Klargjøringsnøkkel for Sted", + "actionListSiteProvisioningKeys": "List Klargjøringsnøkler for Sted", + "actionUpdateSiteProvisioningKey": "Oppdater Klargjøringsnøkkel for Sted", + "actionDeleteSiteProvisioningKey": "Slett Klargjøringsnøkkel for Sted", "noneSelected": "Ingen valgt", "orgNotFound2": "Ingen organisasjoner funnet.", "search": "Søk…", @@ -3811,5 +3815,6 @@ "rdpUploadFailed": "Opplastningen mislyktes", "rdpUnicodeKeyboardMode": "Unicode tastaturmodus", "sessionToolbarShow": "Vis verktøylinje", - "sessionToolbarHide": "Skjul verktøylinje" + "sessionToolbarHide": "Skjul verktøylinje", + "actionUpdateSiteApprovals": "Oppdater Stedsgodkjenninger" } From 2d48adc9f9238c94152017156b5494245d7a7f71 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 16 Jul 2026 22:06:43 -0400 Subject: [PATCH 74/94] use proxy for logout on org auth page session expire --- server/routers/auth/logout.ts | 25 ++++++++++++--------- src/actions/server.ts | 33 ++++++++++++++++++++++++++++ src/components/OrgPolicyRequired.tsx | 27 +++++++++++++---------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/server/routers/auth/logout.ts b/server/routers/auth/logout.ts index b9a1431aa..b36d0c73a 100644 --- a/server/routers/auth/logout.ts +++ b/server/routers/auth/logout.ts @@ -16,18 +16,26 @@ export async function logout( next: NextFunction ): Promise { const { user, session } = await verifySession(req); + const isSecure = req.protocol === "https"; + + // Always clear the session cookie so logout is idempotent, even when + // the session is already missing or invalid + res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); + if (!user || !session) { if (config.getRawConfig().app.log_failed_attempts) { logger.info( - `Log out failed because missing or invalid session. IP: ${req.ip}.` + `Log out with missing or invalid session. IP: ${req.ip}.` ); } - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "You must be logged in to sign out" - ) - ); + + return response(res, { + data: null, + success: true, + error: false, + message: "Logged out successfully", + status: HttpCode.OK + }); } try { @@ -37,9 +45,6 @@ export async function logout( logger.error("Failed to invalidate session", error); } - const isSecure = req.protocol === "https"; - res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure)); - return response(res, { data: null, success: true, diff --git a/src/actions/server.ts b/src/actions/server.ts index 2759e6213..cd75c2c57 100644 --- a/src/actions/server.ts +++ b/src/actions/server.ts @@ -248,6 +248,39 @@ export async function loginProxy( return await makeApiRequest(url, "POST", request); } +export async function logoutProxy(): Promise> { + const env = pullEnv(); + const serverPort = process.env.SERVER_EXTERNAL_PORT; + const url = `http://localhost:${serverPort}/api/v1/auth/logout`; + + const result = await makeApiRequest(url, "POST"); + + try { + const headersList = await reqHeaders(); + const host = headersList.get("host")?.split(":")[0]; + const allCookies = await cookies(); + const clearOptions = { + httpOnly: true, + secure: true, + sameSite: "lax" as const, + path: "/", + maxAge: 0 + }; + // Clear both host-only and domain-scoped variants. + allCookies.set(env.server.sessionCookieName, "", clearOptions); + if (host) { + allCookies.set(env.server.sessionCookieName, "", { + ...clearOptions, + domain: host + }); + } + } catch (cookieError) { + console.error("Failed to clear session cookie:", cookieError); + } + + return result; +} + export async function securityKeyStartProxy( request: SecurityKeyStartRequest, forceLogin?: boolean diff --git a/src/components/OrgPolicyRequired.tsx b/src/components/OrgPolicyRequired.tsx index 3765cd1bf..6d28ddbf5 100644 --- a/src/components/OrgPolicyRequired.tsx +++ b/src/components/OrgPolicyRequired.tsx @@ -12,8 +12,8 @@ import { Shield, ArrowRight } from "lucide-react"; import Link from "next/link"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; -import { createApiClient } from "@app/lib/api"; -import { useEnvContext } from "@app/hooks/useEnvContext"; +import { useState } from "react"; +import { logoutProxy } from "@app/actions/server"; type OrgPolicyRequiredProps = { orgId: string; @@ -40,21 +40,23 @@ export default function OrgPolicyRequired({ }: OrgPolicyRequiredProps) { const t = useTranslations(); const router = useRouter(); - - const api = createApiClient(useEnvContext()); + const [loading, setLoading] = useState(false); const sessionExpired = policies?.maxSessionLength && policies.maxSessionLength.compliant === false; - function reauthenticate() { - api.post("/auth/logout") - .catch(() => {}) - .then(() => { - const destination = redirectAfterAuth ?? `/${orgId}`; - router.push(destination); - router.refresh(); - }); + async function reauthenticate() { + setLoading(true); + try { + await logoutProxy(); + } catch (error) { + console.error("Error during logout:", error); + } finally { + const destination = redirectAfterAuth ?? `/${orgId}`; + router.push(destination); + router.refresh(); + } } if (sessionExpired) { @@ -76,6 +78,7 @@ export default function OrgPolicyRequired({ + + + + + +

+ {t( + "shareAssociateUserDescription" + )} +

+ +
@@ -437,6 +504,34 @@ export default function CreateShareLinkForm({
+
+ + setPersistSession( + val as boolean + ) + } + className="mt-0.5" + /> +
+ +

+ {t( + "sharePersistSessionDescription" + )} +

+
+
+

{t("shareExpireDescription")}

diff --git a/src/components/ShareLinksTable.tsx b/src/components/ShareLinksTable.tsx index 8394edf89..b4df1966f 100644 --- a/src/components/ShareLinksTable.tsx +++ b/src/components/ShareLinksTable.tsx @@ -35,6 +35,7 @@ import moment from "moment"; import CreateShareLinkForm from "@app/components/CreateShareLinkForm"; import { constructShareLink } from "@app/lib/shareLinks"; import { useTranslations } from "next-intl"; +import { getUserDisplayName } from "@app/lib/getUserDisplayName"; export type ShareLinkRow = { accessTokenId: string; @@ -44,6 +45,10 @@ export type ShareLinkRow = { title: string | null; createdAt: number; expiresAt: number | null; + userId?: string | null; + userName?: string | null; + username?: string | null; + userEmail?: string | null; }; type ShareLinksTableProps = { @@ -155,6 +160,41 @@ export default function ShareLinksTable({ ); } }, + { + accessorKey: "userId", + friendlyName: t("user"), + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const r = row.original; + if (!r.userId) { + return -; + } + return ( + + + + ); + } + }, // { // accessorKey: "domain", // header: "Link", diff --git a/src/components/user-selector.tsx b/src/components/user-selector.tsx new file mode 100644 index 000000000..f8ad85eea --- /dev/null +++ b/src/components/user-selector.tsx @@ -0,0 +1,106 @@ +import { orgQueries } from "@app/lib/queries"; +import { getUserDisplayName } from "@app/lib/getUserDisplayName"; +import { useQuery } from "@tanstack/react-query"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "./ui/command"; +import { useMemo, useState } from "react"; +import { useTranslations } from "next-intl"; +import { CheckIcon } from "lucide-react"; +import { cn } from "@app/lib/cn"; +import { useDebounce } from "use-debounce"; +import type { SelectedUser } from "./users-selector"; + +export type { SelectedUser }; + +export type UserSelectorProps = { + orgId: string; + selectedUser?: SelectedUser | null; + onSelectUser: (user: SelectedUser | null) => void; + allowClear?: boolean; +}; + +export function UserSelector({ + orgId, + selectedUser, + onSelectUser, + allowClear = true +}: UserSelectorProps) { + const t = useTranslations(); + const [userSearchQuery, setUserSearchQuery] = useState(""); + const [debouncedValue] = useDebounce(userSearchQuery, 150); + + const { data: users = [] } = useQuery( + orgQueries.users({ orgId, perPage: 10, query: debouncedValue }) + ); + + const usersShown = useMemo(() => { + const allUsers: Array = users.map((u) => ({ + id: u.id, + text: getUserDisplayName(u) + })); + if ( + debouncedValue.trim().length === 0 && + selectedUser && + !allUsers.find((user) => user.id === selectedUser.id) + ) { + allUsers.unshift(selectedUser); + } + return allUsers; + }, [users, selectedUser, debouncedValue]); + + return ( + + + + {t("usersNotFound")} + + {allowClear && ( + { + onSelectUser(null); + }} + > + + {t("none")} + + )} + {usersShown.map((user) => ( + { + onSelectUser(user); + }} + > + + {user.text} + + ))} + + + + ); +} From 6e8283957ce982eb477e9121d9ac6f002e6dfbdb Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 17 Jul 2026 14:06:35 -0400 Subject: [PATCH 77/94] update remote node routes to support access token updates --- messages/en-US.json | 2 +- server/private/routers/hybrid.ts | 319 ++++++++++++++++++++++++++++++- 2 files changed, 318 insertions(+), 3 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index bdd884d45..59b9f86d1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -203,7 +203,7 @@ "expireIn": "Expire In", "neverExpire": "Never expire", "sharePersistSession": "Persist session after first use", - "sharePersistSessionDescription": "When enabled, the first request with this token sets a session cookie so later requests do not need the token. Leave off for API clients that should send the token on every request.", + "sharePersistSessionDescription": "When enabled, the first request with this token via a query param or header sets a session cookie so later requests do not need the token. Leave off for API clients that should send the token on every request.", "shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.", "shareSeeOnce": "You will only be able to see this link once. Make sure to copy it.", "shareAccessHint": "Anyone with this link can access the resource. Share it with care.", diff --git a/server/private/routers/hybrid.ts b/server/private/routers/hybrid.ts index 124db587a..06799bcd6 100644 --- a/server/private/routers/hybrid.ts +++ b/server/private/routers/hybrid.ts @@ -58,7 +58,8 @@ import { resourceRules, resourcePolicyRules, userOrgRoles, - roles + roles, + resourceAccessToken } from "@server/db"; import { eq, and, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm"; import { alias } from "@server/db"; @@ -81,11 +82,16 @@ import config from "@server/lib/config"; import { exchangeSession } from "@server/routers/badger"; import { ResourceSessionValidationResult, + createResourceSession, + serializeResourceSessionCookie, validateResourceSessionToken } from "@server/auth/sessions/resource"; import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes"; import { maxmindLookup } from "@server/db/maxmind"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; +import { generateSessionToken } from "@server/auth/sessions/app"; +import { logAccessAudit } from "#private/lib/logAccessAudit"; +import { getUserOrgRoles } from "@server/lib/userOrgRoles"; import semver from "semver"; import { maxmindAsnLookup } from "@server/db/maxmindAsn"; import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy"; @@ -178,6 +184,73 @@ const validateResourceAccessTokenBodySchema = z.strictObject({ accessToken: z.string() }); +const createAccessTokenSessionParamsSchema = z.strictObject({ + resourceId: z.coerce.number().int().positive() +}); + +const createAccessTokenSessionBodySchema = z.strictObject({ + accessTokenId: z.string().min(1) +}); + +const getAccessTokenParamsSchema = z.strictObject({ + accessTokenId: z.string().min(1) +}); + +const logAccessAuditBodySchema = z.strictObject({ + action: z.boolean(), + type: z.string(), + orgId: z.string(), + resourceId: z.number().optional(), + siteResourceId: z.number().optional(), + user: z + .object({ + username: z.string(), + userId: z.string() + }) + .optional(), + apiKey: z + .object({ + name: z.string().nullable(), + apiKeyId: z.string() + }) + .optional(), + metadata: z.any().optional(), + userAgent: z.string().optional(), + requestIp: z.string().optional() +}); + +type AccessTokenUserData = { + userId: string; + username: string; + email: string | null; + name: string | null; + role: string | null; +}; + +async function resolveAccessTokenUserData( + userId: string, + orgId: string +): Promise { + const [user] = await db + .select() + .from(users) + .where(eq(users.userId, userId)) + .limit(1); + + if (!user) { + return undefined; + } + + const userOrgRoles = await getUserOrgRoles(user.userId, orgId); + return { + userId: user.userId, + username: user.username, + email: user.email, + name: user.name, + role: userOrgRoles.map((r) => r.roleName).join(", ") || null + }; +} + // Certificates by domains query validation const getCertificatesByDomainsQuerySchema = z.strictObject({ // Accept domains as string or array (domains or domains[]) @@ -1829,8 +1902,23 @@ hybridRouter.post( resourceId }); + let userData: AccessTokenUserData | undefined; + if ( + result.valid && + result.tokenItem?.userId && + result.tokenItem.orgId + ) { + userData = await resolveAccessTokenUserData( + result.tokenItem.userId, + result.tokenItem.orgId + ); + } + return response(res, { - data: result, + data: { + ...result, + userData + }, success: true, error: false, message: result.valid @@ -1850,6 +1938,233 @@ hybridRouter.post( } ); +// Create a resource session from a valid access token (for remote nodes) +hybridRouter.post( + "/resource/:resourceId/session/create-access-token", + async (req: Request, res: Response, next: NextFunction) => { + try { + const parsedParams = createAccessTokenSessionParamsSchema.safeParse( + req.params + ); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const parsedBody = createAccessTokenSessionBodySchema.safeParse( + req.body + ); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { resourceId } = parsedParams.data; + const { accessTokenId } = parsedBody.data; + + const [tokenItem] = await db + .select() + .from(resourceAccessToken) + .where(eq(resourceAccessToken.accessTokenId, accessTokenId)) + .limit(1); + + if (!tokenItem || tokenItem.resourceId !== resourceId) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Access token not found" + ) + ); + } + + if (!tokenItem.persistSession) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Access token does not allow session persistence" + ) + ); + } + + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource || !resource.fullDomain) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const token = generateSessionToken(); + const sess = await createResourceSession({ + resourceId: resource.resourceId, + token, + accessTokenId: tokenItem.accessTokenId, + sessionLength: tokenItem.sessionLength, + expiresAt: tokenItem.expiresAt, + doNotExtend: tokenItem.expiresAt ? true : false + }); + + const cookieName = config.getRawConfig().server.session_cookie_name; + const cookie = serializeResourceSessionCookie( + cookieName, + resource.fullDomain, + token, + !resource.ssl, + new Date(sess.expiresAt) + ); + + return response(res, { + data: { cookie }, + success: true, + error: false, + message: "Access token session created successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to create access token session" + ) + ); + } + } +); + +// Resolve access token metadata for remote nodes (cookie session path) +hybridRouter.get( + "/resource/access-token/:accessTokenId", + async (req: Request, res: Response, next: NextFunction) => { + try { + const parsedParams = getAccessTokenParamsSchema.safeParse( + req.params + ); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { accessTokenId } = parsedParams.data; + + const [tokenItem] = await db + .select() + .from(resourceAccessToken) + .where(eq(resourceAccessToken.accessTokenId, accessTokenId)) + .limit(1); + + if (!tokenItem) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Access token not found" + ) + ); + } + + let userData: AccessTokenUserData | undefined; + if (tokenItem.userId) { + userData = await resolveAccessTokenUserData( + tokenItem.userId, + tokenItem.orgId + ); + } + + return response(res, { + data: { tokenItem, userData }, + success: true, + error: false, + message: "Access token retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to get access token" + ) + ); + } + } +); + +// Access audit log from remote nodes +hybridRouter.post( + "/logs/access", + async (req: Request, res: Response, next: NextFunction) => { + try { + const parsedBody = logAccessAuditBodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const remoteExitNode = req.remoteExitNode; + if (!remoteExitNode || !remoteExitNode.exitNodeId) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Remote exit node not found" + ) + ); + } + + if ( + await checkExitNodeOrg( + remoteExitNode.exitNodeId, + parsedBody.data.orgId + ) + ) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "Exit node not allowed for this organization" + ) + ); + } + + await logAccessAudit(parsedBody.data); + + return response(res, { + data: null, + success: true, + error: false, + message: "Access audit log saved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to save access audit log" + ) + ); + } + } +); + const geoIpLookupParamsSchema = z.object({ ip: z.union([z.ipv4(), z.ipv6()]) }); From eb3a3eac98b680a72c04bf3358e1d5f096cedeac Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 17 Jul 2026 15:58:13 -0400 Subject: [PATCH 78/94] add 1.21 migrations --- server/lib/consts.ts | 2 +- server/setup/migrationsPg.ts | 4 +- server/setup/migrationsSqlite.ts | 4 +- server/setup/scriptsPg/1.21.0.ts | 46 ++++++++++++++++++++++ server/setup/scriptsSqlite/1.21.0.ts | 57 ++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 server/setup/scriptsPg/1.21.0.ts create mode 100644 server/setup/scriptsSqlite/1.21.0.ts diff --git a/server/lib/consts.ts b/server/lib/consts.ts index 57a4836b8..9ee4ec4c3 100644 --- a/server/lib/consts.ts +++ b/server/lib/consts.ts @@ -2,7 +2,7 @@ import path from "path"; import { fileURLToPath } from "url"; // This is a placeholder value replaced by the build process -export const APP_VERSION = "1.20.0"; +export const APP_VERSION = "1.21.0"; export const __FILENAME = fileURLToPath(import.meta.url); export const __DIRNAME = path.dirname(__FILENAME); diff --git a/server/setup/migrationsPg.ts b/server/setup/migrationsPg.ts index a271ef96d..4a6d4861c 100644 --- a/server/setup/migrationsPg.ts +++ b/server/setup/migrationsPg.ts @@ -27,6 +27,7 @@ import m18 from "./scriptsPg/1.18.3"; import m19 from "./scriptsPg/1.18.4"; import m20 from "./scriptsPg/1.19.0"; import m21 from "./scriptsPg/1.20.0"; +import m22 from "./scriptsPg/1.21.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -53,7 +54,8 @@ const migrations = [ { version: "1.18.3", run: m18 }, { version: "1.18.4", run: m19 }, { version: "1.19.0", run: m20 }, - { version: "1.20.0", run: m21 } + { version: "1.20.0", run: m21 }, + { version: "1.21.0", run: m22 } // Add new migrations here as they are created ] as { version: string; diff --git a/server/setup/migrationsSqlite.ts b/server/setup/migrationsSqlite.ts index a8f6f9e33..c6b4debef 100644 --- a/server/setup/migrationsSqlite.ts +++ b/server/setup/migrationsSqlite.ts @@ -46,6 +46,7 @@ import m40 from "./scriptsSqlite/1.18.4"; import m41 from "./scriptsSqlite/1.19.0"; import m42 from "./scriptsSqlite/1.19.1"; import m43 from "./scriptsSqlite/1.20.0"; +import m44 from "./scriptsSqlite/1.21.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -89,7 +90,8 @@ const migrations = [ { version: "1.18.4", run: m40 }, { version: "1.19.0", run: m41 }, { version: "1.19.1", run: m42 }, - { version: "1.20.0", run: m43 } + { version: "1.20.0", run: m43 }, + { version: "1.21.0", run: m44 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scriptsPg/1.21.0.ts b/server/setup/scriptsPg/1.21.0.ts new file mode 100644 index 000000000..3a6538f8c --- /dev/null +++ b/server/setup/scriptsPg/1.21.0.ts @@ -0,0 +1,46 @@ +import { db } from "@server/db/pg/driver"; +import { sql } from "drizzle-orm"; + +const version = "1.21.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + await db.execute(sql`BEGIN`); + + await db.execute(sql` + ALTER TABLE "resourceAccessToken" ADD COLUMN "userId" varchar; + `); + + await db.execute(sql` + ALTER TABLE "resourceAccessToken" ADD COLUMN "persistSession" boolean DEFAULT false NOT NULL; + `); + + await db.execute(sql` + ALTER TABLE "resources" ADD COLUMN "status" varchar DEFAULT 'approved'; + `); + + await db.execute(sql` + ALTER TABLE "siteResources" ADD COLUMN "status" varchar DEFAULT 'approved'; + `); + + await db.execute(sql` + ALTER TABLE "sites" ADD COLUMN "localEndpoints" varchar; + `); + + await db.execute(sql` + ALTER TABLE "resourceAccessToken" ADD CONSTRAINT "resourceAccessToken_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; + `); + + await db.execute(sql`COMMIT`); + console.log("Migrated database"); + } catch (e) { + await db.execute(sql`ROLLBACK`); + console.log("Unable to migrate database"); + console.log(e); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/server/setup/scriptsSqlite/1.21.0.ts b/server/setup/scriptsSqlite/1.21.0.ts new file mode 100644 index 000000000..8fb635044 --- /dev/null +++ b/server/setup/scriptsSqlite/1.21.0.ts @@ -0,0 +1,57 @@ +import { APP_PATH } from "@server/lib/consts"; +import Database from "better-sqlite3"; +import path from "path"; + +const version = "1.21.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + const location = path.join(APP_PATH, "db", "db.sqlite"); + const db = new Database(location); + + try { + db.pragma("foreign_keys = OFF"); + + db.transaction(() => { + db.prepare( + ` + ALTER TABLE 'resourceAccessToken' ADD 'userId' text REFERENCES user(id); + ` + ).run(); + + db.prepare( + ` + ALTER TABLE 'resourceAccessToken' ADD 'persistSession' integer DEFAULT false NOT NULL; + ` + ).run(); + + db.prepare( + ` + ALTER TABLE 'resources' ADD 'status' text DEFAULT 'approved'; + ` + ).run(); + + db.prepare( + ` + ALTER TABLE 'siteResources' ADD 'status' text DEFAULT 'approved'; + ` + ).run(); + + db.prepare( + ` + ALTER TABLE 'sites' ADD 'localEndpoints' text; + ` + ).run(); + })(); + + db.pragma("foreign_keys = ON"); + + console.log("Migrated database"); + } catch (e) { + console.log("Failed to migrate db:", e); + throw e; + } + + console.log(`${version} migration complete`); +} From a0b9ea76a3ee5495511f76c3640f95d7da0651b3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Fri, 17 Jul 2026 16:22:15 -0400 Subject: [PATCH 79/94] add badger to migration --- server/setup/scriptsPg/1.21.0.ts | 49 ++++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.21.0.ts | 47 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/server/setup/scriptsPg/1.21.0.ts b/server/setup/scriptsPg/1.21.0.ts index 3a6538f8c..d5195c382 100644 --- a/server/setup/scriptsPg/1.21.0.ts +++ b/server/setup/scriptsPg/1.21.0.ts @@ -1,5 +1,11 @@ import { db } from "@server/db/pg/driver"; +import { APP_PATH } from "@server/lib/consts"; import { sql } from "drizzle-orm"; +import fs from "fs"; +import yaml from "js-yaml"; +import path from "path"; +import z from "zod"; +import { fromZodError } from "zod-validation-error"; const version = "1.21.0"; @@ -42,5 +48,48 @@ export default async function migration() { throw e; } + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + const schema = z.object({ + experimental: z.object({ + plugins: z.object({ + badger: z.object({ + moduleName: z.string(), + version: z.string() + }) + }) + }) + }); + + const traefikFileContents = fs.readFileSync(traefikPath, "utf8"); + const traefikConfig = yaml.load(traefikFileContents) as any; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.5.0"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to v1.5.0" + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information." + ); + console.error(e); + } + console.log(`${version} migration complete`); } diff --git a/server/setup/scriptsSqlite/1.21.0.ts b/server/setup/scriptsSqlite/1.21.0.ts index 8fb635044..b710c783b 100644 --- a/server/setup/scriptsSqlite/1.21.0.ts +++ b/server/setup/scriptsSqlite/1.21.0.ts @@ -1,6 +1,10 @@ import { APP_PATH } from "@server/lib/consts"; import Database from "better-sqlite3"; +import fs from "fs"; +import yaml from "js-yaml"; import path from "path"; +import z from "zod"; +import { fromZodError } from "zod-validation-error"; const version = "1.21.0"; @@ -53,5 +57,48 @@ export default async function migration() { throw e; } + try { + const traefikPath = path.join( + APP_PATH, + "traefik", + "traefik_config.yml" + ); + + const schema = z.object({ + experimental: z.object({ + plugins: z.object({ + badger: z.object({ + moduleName: z.string(), + version: z.string() + }) + }) + }) + }); + + const traefikFileContents = fs.readFileSync(traefikPath, "utf8"); + const traefikConfig = yaml.load(traefikFileContents) as any; + + const parsedConfig = schema.safeParse(traefikConfig); + + if (!parsedConfig.success) { + throw new Error(fromZodError(parsedConfig.error).toString()); + } + + traefikConfig.experimental.plugins.badger.version = "v1.5.0"; + + const updatedTraefikYaml = yaml.dump(traefikConfig); + + fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8"); + + console.log( + "Updated the version of Badger in your Traefik configuration to v1.5.0" + ); + } catch (e) { + console.log( + "We were unable to update the version of Badger in your Traefik configuration. Please update it manually. Check the release notes for this version for more information." + ); + console.error(e); + } + console.log(`${version} migration complete`); } From 02fe1f3abdff200a1167f9bd99376f67891231f4 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 18 Jul 2026 11:53:38 -0400 Subject: [PATCH 80/94] Claify that this is only for the cloud --- .../emails/templates/NotifyTrialExpiring.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/emails/templates/NotifyTrialExpiring.tsx b/server/emails/templates/NotifyTrialExpiring.tsx index 7c712e278..a4c09e152 100644 --- a/server/emails/templates/NotifyTrialExpiring.tsx +++ b/server/emails/templates/NotifyTrialExpiring.tsx @@ -30,14 +30,14 @@ export const NotifyTrialExpiring = ({ const isLastDay = daysRemaining === 1; const previewText = hasEnded - ? `Your trial for ${orgName} has ended.` + ? `Your cloud trial for ${orgName} has ended.` : isLastDay - ? `Your trial for ${orgName} ends tomorrow.` - : `Your trial for ${orgName} ends in ${daysRemaining} days.`; + ? `Your cloud trial for ${orgName} ends tomorrow.` + : `Your cloud trial for ${orgName} ends in ${daysRemaining} days.`; const heading = hasEnded - ? "Your Trial Ended" - : "Your Trial is Ending Soon"; + ? "Your Cloud Trial Ended" + : "Your Cloud Trial is Ending Soon"; return ( @@ -55,7 +55,7 @@ export const NotifyTrialExpiring = ({ {hasEnded ? ( <> - Your free trial for{" "} + Your cloud free trial for{" "} {orgName} ended on{" "} {trialEndsAt}. Your account has been moved to the free plan, which @@ -64,10 +64,11 @@ export const NotifyTrialExpiring = ({ Some features and resources may now be - restricted. To restore full - access and continue using all the features - you had during your trial, please upgrade to - a paid plan. + restricted. To restore full access and + continue using all the features you had + during your trial, please upgrade to a paid + plan. This does not effect any self hosted + licenses. @@ -93,7 +94,8 @@ export const NotifyTrialExpiring = ({ After your trial ends, your account will be moved to the free plan and some - functionality may be restricted. + functionality may be restricted. This does + not effect any self hosted licenses. From 9e2ec72ced565578ccc4f916bc4917871adfa97e Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 18 Jul 2026 16:15:30 -0400 Subject: [PATCH 81/94] Fix missing resource id in cache --- server/routers/badger/verifySession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 901c881f2..33c75b101 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -418,7 +418,7 @@ export async function verifyResourceSession( } // check for HTTP Basic Auth header - const clientHeaderAuthKey = `headerAuth:${clientHeaderAuth}`; + const clientHeaderAuthKey = `headerAuth:${resource.resourceId}:${clientHeaderAuth}`; if (headerAuth && clientHeaderAuth) { if (localCache.get(clientHeaderAuthKey)) { logger.debug( From a2e1c7b751f660983ac46641ad8a74a9d8c418f2 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 18 Jul 2026 16:30:29 -0400 Subject: [PATCH 82/94] Go to domains if no domains --- messages/en-US.json | 3 +++ src/components/DomainPicker.tsx | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/messages/en-US.json b/messages/en-US.json index 59b9f86d1..2ca25d850 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1995,6 +1995,9 @@ "domainPickerSubdomain": "Subdomain: {subdomain}", "domainPickerNamespace": "Namespace: {namespace}", "domainPickerShowMore": "Show More", + "domainPickerNoDomainsAvailableTitle": "No domains available", + "domainPickerNoDomainsAvailableDescription": "You don't have any domains set up yet. Create a domain to continue.", + "domainPickerNoDomainsAvailableAction": "Go to Domains", "regionSelectorTitle": "Select Region", "domainPickerRemoteExitNodeWarning": "Provided domains are not supported when sites connect to remote exit nodes. For resources to be available on remote nodes, use a custom domain instead.", "regionSelectorInfo": "Selecting a region helps us provide better performance for your location. You do not have to be in the same region as your server.", diff --git a/src/components/DomainPicker.tsx b/src/components/DomainPicker.tsx index daacf892b..e06c3e57d 100644 --- a/src/components/DomainPicker.tsx +++ b/src/components/DomainPicker.tsx @@ -1,6 +1,6 @@ "use client"; -import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { @@ -43,10 +43,12 @@ import { CheckCircle2, ChevronsUpDown, ExternalLink, + Globe, KeyRound, Zap } from "lucide-react"; import { useTranslations } from "next-intl"; +import Link from "next/link"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { usePaidStatus } from "@/hooks/usePaidStatus"; import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix"; @@ -494,6 +496,28 @@ export default function DomainPicker({ const hasMoreProvided = sortedAvailableOptions.length > providedDomainsShown; + const noDomainsAvailable = + !loadingDomains && + organizationDomains.length === 0 && + (build === "oss" || hideFreeDomain || requiresPaywall); + + if (noDomainsAvailable) { + return ( + + + {t("domainPickerNoDomainsAvailableTitle")} + +

{t("domainPickerNoDomainsAvailableDescription")}

+ +
+
+ ); + } + return (
From 9561d23f1e77cdea5d69db820639c0ebc62f03e1 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 19 Jul 2026 14:41:07 -0400 Subject: [PATCH 83/94] Resovle endcoding issue --- server/lib/pathMatch.ts | 37 +++++++++++++++++++-- server/routers/badger/verifySession.test.ts | 32 ++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/server/lib/pathMatch.ts b/server/lib/pathMatch.ts index a007f9ec3..8ef579607 100644 --- a/server/lib/pathMatch.ts +++ b/server/lib/pathMatch.ts @@ -15,10 +15,41 @@ function getSegmentRegex(patternPart: string): RegExp { return regex; } +// Decodes percent-encoding (so an encoded slash like `%2F` is treated as a +// real path separator, matching what most backends will do) and then +// resolves `.` / `..` segments, so a request like `/public%2F..%2Fadmin/` +// or `/public/../admin/` is matched as `/admin/`, not as a literal segment +// or a wildcard-swallowed sequence under `/public/*`. +function decodeAndResolvePath(p: string): string[] { + const rawParts = p.split("/").filter(Boolean); + + const resolved: string[] = []; + for (const rawPart of rawParts) { + let part: string; + try { + part = decodeURIComponent(rawPart); + } catch { + part = rawPart; + } + + // an encoded slash can turn one raw segment into several real ones + for (const segment of part.split("/").filter(Boolean)) { + if (segment === ".") { + continue; + } else if (segment === "..") { + resolved.pop(); + } else { + resolved.push(segment); + } + } + } + + return resolved; +} + export function isPathAllowed(pattern: string, path: string): boolean { - const normalize = (p: string) => p.split("/").filter(Boolean); - const patternParts = normalize(pattern); - const pathParts = normalize(path); + const patternParts = pattern.split("/").filter(Boolean); + const pathParts = decodeAndResolvePath(path); function matchSegments( patternIndex: number, diff --git a/server/routers/badger/verifySession.test.ts b/server/routers/badger/verifySession.test.ts index 681717f9c..c91805f8e 100644 --- a/server/routers/badger/verifySession.test.ts +++ b/server/routers/badger/verifySession.test.ts @@ -236,6 +236,38 @@ function runTests() { "Root path should not match non-root path" ); + // Path traversal / encoded-slash bypass regression tests + assertEquals( + isPathAllowed("public/*", "public/../admin"), + false, + "Literal .. traversal out of an allowed prefix must not match" + ); + assertEquals( + isPathAllowed("public/*", "public%2F..%2Fadmin"), + false, + "Encoded-slash traversal out of an allowed prefix must not match" + ); + assertEquals( + isPathAllowed("public/*", "public%2f..%2fadmin%2ffile"), + false, + "Encoded-slash traversal is case-insensitively decoded before matching" + ); + assertEquals( + isPathAllowed("admin/*", "public/../admin/secret"), + true, + ".. traversal INTO a restricted path should still be caught by its own rule" + ); + assertEquals( + isPathAllowed("public/*", "public%2Ffoo"), + true, + "Encoded slash without traversal should still resolve and match normally" + ); + assertEquals( + isPathAllowed("public/*", "public/./foo"), + true, + "Single-dot segments are a no-op and should not affect matching" + ); + console.log("All path matching tests passed!"); } From 4d8cb7e2311f3bf4987bc40b1bb15dab652bcc58 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 9 Jul 2026 20:23:19 -0400 Subject: [PATCH 84/94] Add aliaes for public/private resources --- .../accessToken/generateAccessToken.ts | 33 +++ .../routers/accessToken/listAccessTokens.ts | 29 +++ server/routers/integration.ts | 223 +++++++++++++----- server/routers/policy/getResourcePolicy.ts | 13 + .../policy/setResourcePolicyAccessControl.ts | 19 ++ .../policy/setResourcePolicyHeaderAuth.ts | 19 ++ .../policy/setResourcePolicyPassword.ts | 19 ++ .../policy/setResourcePolicyPincode.ts | 19 ++ .../routers/policy/setResourcePolicyRules.ts | 19 ++ .../policy/setResourcePolicyWhitelist.ts | 19 ++ server/routers/policy/updateResourcePolicy.ts | 18 ++ .../resource/addEmailToResourceWhitelist.ts | 33 +++ server/routers/resource/addRoleToResource.ts | 34 +++ server/routers/resource/addUserToResource.ts | 34 +++ server/routers/resource/createResource.ts | 33 +++ server/routers/resource/createResourceRule.ts | 33 +++ server/routers/resource/deleteResource.ts | 26 ++ server/routers/resource/deleteResourceRule.ts | 26 ++ server/routers/resource/getResource.ts | 28 +++ .../routers/resource/getResourcePolicies.ts | 11 + .../routers/resource/getResourceWhitelist.ts | 26 ++ .../routers/resource/listAllResourceNames.ts | 28 +++ server/routers/resource/listResourceRoles.ts | 26 ++ server/routers/resource/listResourceRules.ts | 27 +++ server/routers/resource/listResourceUsers.ts | 26 ++ server/routers/resource/listResources.ts | 29 +++ .../removeEmailFromResourceWhitelist.ts | 33 +++ .../resource/removeRoleFromResource.ts | 33 +++ .../resource/removeUserFromResource.ts | 33 +++ .../routers/resource/setResourceHeaderAuth.ts | 34 +++ .../routers/resource/setResourcePassword.ts | 34 +++ server/routers/resource/setResourcePincode.ts | 34 +++ server/routers/resource/setResourceRoles.ts | 34 +++ server/routers/resource/setResourceUsers.ts | 34 +++ .../routers/resource/setResourceWhitelist.ts | 34 +++ server/routers/resource/updateResource.ts | 36 +++ server/routers/resource/updateResourceRule.ts | 33 +++ .../siteResource/addClientToSiteResource.ts | 34 +++ .../siteResource/addRoleToSiteResource.ts | 33 +++ .../siteResource/addUserToSiteResource.ts | 33 +++ .../batchAddClientToSiteResources.ts | 33 +++ .../siteResource/createSiteResource.ts | 33 +++ .../siteResource/deleteSiteResource.ts | 26 ++ .../routers/siteResource/getSiteResource.ts | 30 +++ .../siteResource/listAllSiteResourcesByOrg.ts | 27 +++ .../siteResource/listSiteResourceClients.ts | 26 ++ .../siteResource/listSiteResourceRoles.ts | 26 ++ .../siteResource/listSiteResourceUsers.ts | 26 ++ .../routers/siteResource/listSiteResources.ts | 27 +++ .../removeClientFromSiteResource.ts | 34 +++ .../removeRoleFromSiteResource.ts | 33 +++ .../removeUserFromSiteResource.ts | 33 +++ .../siteResource/setSiteResourceClients.ts | 34 +++ .../siteResource/setSiteResourceRoles.ts | 34 +++ .../siteResource/setSiteResourceUsers.ts | 34 +++ .../siteResource/updateSiteResource.ts | 33 +++ server/routers/target/createTarget.ts | 33 +++ server/routers/target/listTargets.ts | 27 +++ 58 files changed, 1812 insertions(+), 59 deletions(-) diff --git a/server/routers/accessToken/generateAccessToken.ts b/server/routers/accessToken/generateAccessToken.ts index 5b259af70..c46e94162 100644 --- a/server/routers/accessToken/generateAccessToken.ts +++ b/server/routers/accessToken/generateAccessToken.ts @@ -74,6 +74,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/access-token", + description: "Generate a new access token for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken], + request: { + params: generateAccssTokenParamsSchema, + body: { + content: { + "application/json": { + schema: generateAccessTokenBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function generateAccessToken( req: Request, res: Response, diff --git a/server/routers/accessToken/listAccessTokens.ts b/server/routers/accessToken/listAccessTokens.ts index a5ec3b540..0068e459a 100644 --- a/server/routers/accessToken/listAccessTokens.ts +++ b/server/routers/accessToken/listAccessTokens.ts @@ -184,6 +184,35 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/access-tokens", + description: "List all access tokens for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken], + request: { + params: z.object({ + resourceId: z.number() + }), + query: listAccessTokensSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listAccessTokens( req: Request, res: Response, diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 567c3060e..d16efdb63 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -163,7 +163,7 @@ authenticated.get( // Site Resource endpoints authenticated.put( - "/org/:orgId/site-resource", + ["/org/:orgId/site-resource", "/org/:orgId/private-resource"], verifyApiKeyOrgAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.createSiteResource), @@ -172,7 +172,10 @@ authenticated.put( ); authenticated.get( - "/org/:orgId/site/:siteId/resources", + [ + "/org/:orgId/site/:siteId/resources", + "/org/:orgId/site/:siteId/private-resources" + ], verifyApiKeyOrgAccess, verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.listSiteResources), @@ -180,21 +183,21 @@ authenticated.get( ); authenticated.get( - "/org/:orgId/site-resources", + ["/org/:orgId/site-resources", "/org/:orgId/private-resources"], verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.listSiteResources), siteResource.listAllSiteResourcesByOrg ); authenticated.get( - "/site-resource/:siteResourceId", + ["/site-resource/:siteResourceId", "/private-resource/:siteResourceId"], verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.getSiteResource), siteResource.getSiteResource ); authenticated.post( - "/site-resource/:siteResourceId", + ["/site-resource/:siteResourceId", "/private-resource/:siteResourceId"], verifyApiKeySiteResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.updateSiteResource), @@ -203,7 +206,7 @@ authenticated.post( ); authenticated.delete( - "/site-resource/:siteResourceId", + ["/site-resource/:siteResourceId", "/private-resource/:siteResourceId"], verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteSiteResource), logActionAudit(ActionsEnum.deleteSiteResource), @@ -211,28 +214,40 @@ authenticated.delete( ); authenticated.get( - "/site-resource/:siteResourceId/roles", + [ + "/site-resource/:siteResourceId/roles", + "/private-resource/:siteResourceId/roles" + ], verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceRoles), siteResource.listSiteResourceRoles ); authenticated.get( - "/site-resource/:siteResourceId/users", + [ + "/site-resource/:siteResourceId/users", + "/private-resource/:siteResourceId/users" + ], verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceUsers), siteResource.listSiteResourceUsers ); authenticated.get( - "/site-resource/:siteResourceId/clients", + [ + "/site-resource/:siteResourceId/clients", + "/private-resource/:siteResourceId/clients" + ], verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceUsers), siteResource.listSiteResourceClients ); authenticated.post( - "/site-resource/:siteResourceId/roles", + [ + "/site-resource/:siteResourceId/roles", + "/private-resource/:siteResourceId/roles" + ], verifyApiKeySiteResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -242,7 +257,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/users", + [ + "/site-resource/:siteResourceId/users", + "/private-resource/:siteResourceId/users" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -252,7 +270,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/roles/add", + [ + "/site-resource/:siteResourceId/roles/add", + "/private-resource/:siteResourceId/roles/add" + ], verifyApiKeySiteResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -262,7 +283,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/roles/remove", + [ + "/site-resource/:siteResourceId/roles/remove", + "/private-resource/:siteResourceId/roles/remove" + ], verifyApiKeySiteResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -272,7 +296,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/users/add", + [ + "/site-resource/:siteResourceId/users/add", + "/private-resource/:siteResourceId/users/add" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -282,7 +309,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/users/remove", + [ + "/site-resource/:siteResourceId/users/remove", + "/private-resource/:siteResourceId/users/remove" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -292,7 +322,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/clients", + [ + "/site-resource/:siteResourceId/clients", + "/private-resource/:siteResourceId/clients" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceClients, verifyLimits, @@ -302,7 +335,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/clients/add", + [ + "/site-resource/:siteResourceId/clients/add", + "/private-resource/:siteResourceId/clients/add" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceClients, verifyLimits, @@ -312,7 +348,10 @@ authenticated.post( ); authenticated.post( - "/site-resource/:siteResourceId/clients/remove", + [ + "/site-resource/:siteResourceId/clients/remove", + "/private-resource/:siteResourceId/clients/remove" + ], verifyApiKeySiteResourceAccess, verifyApiKeySetResourceClients, verifyLimits, @@ -322,7 +361,7 @@ authenticated.post( ); authenticated.post( - "/client/:clientId/site-resources", + ["/client/:clientId/site-resources", "/client/:clientId/private-resources"], verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourceUsers), logActionAudit(ActionsEnum.setResourceUsers), @@ -330,7 +369,7 @@ authenticated.post( ); authenticated.put( - "/org/:orgId/resource", + ["/org/:orgId/resource", "/org/:orgId/public-resource"], verifyApiKeyOrgAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.createResource), @@ -339,7 +378,10 @@ authenticated.put( ); authenticated.put( - "/org/:orgId/site/:siteId/resource", + [ + "/org/:orgId/site/:siteId/resource", + "/org/:orgId/site/:siteId/public-resource" + ], verifyApiKeyOrgAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.createResource), @@ -348,14 +390,14 @@ authenticated.put( ); authenticated.get( - "/site/:siteId/resources", + ["/site/:siteId/resources", "/site/:siteId/public-resources"], verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.listResources), resource.listResources ); authenticated.get( - "/org/:orgId/resources", + ["/org/:orgId/resources", "/org/:orgId/public-resources"], verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.listResources), resource.listResources @@ -443,42 +485,45 @@ authenticated.delete( ); authenticated.get( - "/resource/:resourceId/roles", + ["/resource/:resourceId/roles", "/public-resource/:resourceId/roles"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceRoles), resource.listResourceRoles ); authenticated.get( - "/resource/:resourceId/users", + ["/resource/:resourceId/users", "/public-resource/:resourceId/users"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceUsers), resource.listResourceUsers ); authenticated.get( - "/resource/:resourceId", + ["/resource/:resourceId", "/public-resource/:resourceId"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.getResource), resource.getResource ); authenticated.get( - "/resource-policy/:resourcePolicyId", + [ + "/resource-policy/:resourcePolicyId", + "/public-resource-policy/:resourcePolicyId" + ], verifyApiKeyResourcePolicyAccess, verifyApiKeyHasAction(ActionsEnum.getResourcePolicy), policy.getResourcePolicy ); authenticated.get( - "/resource/:resourceId/policies", + ["/resource/:resourceId/policies", "/public-resource/:resourceId/policies"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.getResourcePolicy), resource.getResourcePolicies ); authenticated.post( - "/resource/:resourceId", + ["/resource/:resourceId", "/public-resource/:resourceId"], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.updateResource), @@ -487,14 +532,17 @@ authenticated.post( ); authenticated.put( - "/resource-policy/:resourcePolicyId", + [ + "/resource-policy/:resourcePolicyId", + "/public-resource-policy/:resourcePolicyId" + ], verifyApiKeyResourcePolicyAccess, verifyApiKeyHasAction(ActionsEnum.updateResourcePolicy), policy.updateResourcePolicy ); authenticated.delete( - "/resource/:resourceId", + ["/resource/:resourceId", "/public-resource/:resourceId"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteResource), logActionAudit(ActionsEnum.deleteResource), @@ -502,7 +550,7 @@ authenticated.delete( ); authenticated.put( - "/resource/:resourceId/target", + ["/resource/:resourceId/target", "/public-resource/:resourceId/target"], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.createTarget), @@ -511,14 +559,14 @@ authenticated.put( ); authenticated.get( - "/resource/:resourceId/targets", + ["/resource/:resourceId/targets", "/public-resource/:resourceId/targets"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.listTargets), target.listTargets ); authenticated.put( - "/resource/:resourceId/rule", + ["/resource/:resourceId/rule", "/public-resource/:resourceId/rule"], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.createResourceRule), @@ -527,14 +575,17 @@ authenticated.put( ); authenticated.get( - "/resource/:resourceId/rules", + ["/resource/:resourceId/rules", "/public-resource/:resourceId/rules"], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.listResourceRules), resource.listResourceRules ); authenticated.post( - "/resource/:resourceId/rule/:ruleId", + [ + "/resource/:resourceId/rule/:ruleId", + "/public-resource/:resourceId/rule/:ruleId" + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.updateResourceRule), @@ -543,7 +594,10 @@ authenticated.post( ); authenticated.delete( - "/resource/:resourceId/rule/:ruleId", + [ + "/resource/:resourceId/rule/:ruleId", + "/public-resource/:resourceId/rule/:ruleId" + ], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteResourceRule), logActionAudit(ActionsEnum.deleteResourceRule), @@ -625,7 +679,7 @@ authenticated.post( ); authenticated.post( - "/resource/:resourceId/roles", + ["/resource/:resourceId/roles", "/public-resource/:resourceId/roles"], verifyApiKeyResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -635,7 +689,7 @@ authenticated.post( ); authenticated.post( - "/resource/:resourceId/users", + ["/resource/:resourceId/users", "/public-resource/:resourceId/users"], verifyApiKeyResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -645,7 +699,10 @@ authenticated.post( ); authenticated.put( - "/resource-policy/:resourcePolicyId/access-control", + [ + "/resource-policy/:resourcePolicyId/access-control", + "/public-resource-policy/:resourcePolicyId/access-control" + ], verifyApiKeyResourcePolicyAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -657,7 +714,10 @@ authenticated.put( ); authenticated.put( - "/resource-policy/:resourcePolicyId/password", + [ + "/resource-policy/:resourcePolicyId/password", + "/public-resource-policy/:resourcePolicyId/password" + ], verifyApiKeyResourcePolicyAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePolicyPassword), @@ -666,7 +726,10 @@ authenticated.put( ); authenticated.put( - "/resource-policy/:resourcePolicyId/pincode", + [ + "/resource-policy/:resourcePolicyId/pincode", + "/public-resource-policy/:resourcePolicyId/pincode" + ], verifyApiKeyResourcePolicyAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePolicyPincode), @@ -675,7 +738,10 @@ authenticated.put( ); authenticated.put( - "/resource-policy/:resourcePolicyId/header-auth", + [ + "/resource-policy/:resourcePolicyId/header-auth", + "/public-resource-policy/:resourcePolicyId/header-auth" + ], verifyApiKeyResourcePolicyAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePolicyHeaderAuth), @@ -684,7 +750,10 @@ authenticated.put( ); authenticated.put( - "/resource-policy/:resourcePolicyId/whitelist", + [ + "/resource-policy/:resourcePolicyId/whitelist", + "/public-resource-policy/:resourcePolicyId/whitelist" + ], verifyApiKeyResourcePolicyAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePolicyWhitelist), @@ -693,7 +762,10 @@ authenticated.put( ); authenticated.put( - "/resource-policy/:resourcePolicyId/rules", + [ + "/resource-policy/:resourcePolicyId/rules", + "/public-resource-policy/:resourcePolicyId/rules" + ], verifyApiKeyResourcePolicyAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePolicyRules), @@ -702,7 +774,10 @@ authenticated.put( ); authenticated.post( - "/resource/:resourceId/roles/add", + [ + "/resource/:resourceId/roles/add", + "/public-resource/:resourceId/roles/add" + ], verifyApiKeyResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -712,7 +787,10 @@ authenticated.post( ); authenticated.post( - "/resource/:resourceId/roles/remove", + [ + "/resource/:resourceId/roles/remove", + "/public-resource/:resourceId/roles/remove" + ], verifyApiKeyResourceAccess, verifyApiKeyRoleAccess, verifyLimits, @@ -722,7 +800,10 @@ authenticated.post( ); authenticated.post( - "/resource/:resourceId/users/add", + [ + "/resource/:resourceId/users/add", + "/public-resource/:resourceId/users/add" + ], verifyApiKeyResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -732,7 +813,10 @@ authenticated.post( ); authenticated.post( - "/resource/:resourceId/users/remove", + [ + "/resource/:resourceId/users/remove", + "/public-resource/:resourceId/users/remove" + ], verifyApiKeyResourceAccess, verifyApiKeySetResourceUsers, verifyLimits, @@ -742,7 +826,7 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/password`, + [`/resource/:resourceId/password`, `/public-resource/:resourceId/password`], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePassword), @@ -751,7 +835,7 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/pincode`, + [`/resource/:resourceId/pincode`, `/public-resource/:resourceId/pincode`], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourcePincode), @@ -760,7 +844,10 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/header-auth`, + [ + `/resource/:resourceId/header-auth`, + `/public-resource/:resourceId/header-auth` + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourceHeaderAuth), @@ -769,7 +856,10 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/whitelist`, + [ + `/resource/:resourceId/whitelist`, + `/public-resource/:resourceId/whitelist` + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), @@ -778,7 +868,10 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/whitelist/add`, + [ + `/resource/:resourceId/whitelist/add`, + `/public-resource/:resourceId/whitelist/add` + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), @@ -786,7 +879,10 @@ authenticated.post( ); authenticated.post( - `/resource/:resourceId/whitelist/remove`, + [ + `/resource/:resourceId/whitelist/remove`, + `/public-resource/:resourceId/whitelist/remove` + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), @@ -794,14 +890,20 @@ authenticated.post( ); authenticated.get( - `/resource/:resourceId/whitelist`, + [ + `/resource/:resourceId/whitelist`, + `/public-resource/:resourceId/whitelist` + ], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.getResourceWhitelist), resource.getResourceWhitelist ); authenticated.post( - `/resource/:resourceId/access-token`, + [ + `/resource/:resourceId/access-token`, + `/public-resource/:resourceId/access-token` + ], verifyApiKeyResourceAccess, verifyLimits, verifyApiKeyHasAction(ActionsEnum.generateAccessToken), @@ -825,7 +927,10 @@ authenticated.get( ); authenticated.get( - `/resource/:resourceId/access-tokens`, + [ + `/resource/:resourceId/access-tokens`, + `/public-resource/:resourceId/access-tokens` + ], verifyApiKeyResourceAccess, verifyApiKeyHasAction(ActionsEnum.listAccessTokens), accessToken.listAccessTokens @@ -1155,7 +1260,7 @@ authenticated.get( ); authenticated.get( - "/org/:orgId/resource-names", + ["/org/:orgId/resource-names", "/org/:orgId/public-resource-names"], verifyApiKeyOrgAccess, verifyApiKeyHasAction(ActionsEnum.listResources), resource.listAllResourceNames diff --git a/server/routers/policy/getResourcePolicy.ts b/server/routers/policy/getResourcePolicy.ts index d7513d58d..fa9102082 100644 --- a/server/routers/policy/getResourcePolicy.ts +++ b/server/routers/policy/getResourcePolicy.ts @@ -191,6 +191,19 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "get", + path: "/public-resource-policy/{resourcePolicyId}", + description: "Get a resource policy by its resourcePolicyId.", + tags: [OpenAPITags.Policy], + request: { + params: z.object({ + resourcePolicyId: z.number() + }) + }, + responses: {} +}); + export async function getResourcePolicy( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyAccessControl.ts b/server/routers/policy/setResourcePolicyAccessControl.ts index 6c0e19b68..c59390df1 100644 --- a/server/routers/policy/setResourcePolicyAccessControl.ts +++ b/server/routers/policy/setResourcePolicyAccessControl.ts @@ -54,6 +54,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "post", + path: "/public-resource-policy/{resourceId}/access-control", + description: + "Set access control users for a resource policy, including SSO, users, roles, Identity provider.", + tags: [OpenAPITags.Policy, OpenAPITags.User], + request: { + params: setResourcePolicyAccessControlParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyAcccessControlBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyAccessControl( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyHeaderAuth.ts b/server/routers/policy/setResourcePolicyHeaderAuth.ts index 6dba0b8a7..d47dd7239 100644 --- a/server/routers/policy/setResourcePolicyHeaderAuth.ts +++ b/server/routers/policy/setResourcePolicyHeaderAuth.ts @@ -43,6 +43,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/header-auth", + description: + "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.", + tags: [OpenAPITags.Policy], + request: { + params: setResourcePolicyHeaderAuthParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyHeaderAuthBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyHeaderAuth( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyPassword.ts b/server/routers/policy/setResourcePolicyPassword.ts index dd436a7bf..1ab2e549a 100644 --- a/server/routers/policy/setResourcePolicyPassword.ts +++ b/server/routers/policy/setResourcePolicyPassword.ts @@ -38,6 +38,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/password", + description: + "Set the password for a resource policy. Setting the password to null will remove it.", + tags: [OpenAPITags.Policy], + request: { + params: setResourcePolicyPasswordParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPasswordBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyPassword( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyPincode.ts b/server/routers/policy/setResourcePolicyPincode.ts index f99691275..c6849cc87 100644 --- a/server/routers/policy/setResourcePolicyPincode.ts +++ b/server/routers/policy/setResourcePolicyPincode.ts @@ -41,6 +41,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/pincode", + description: + "Set the PIN code for a resource policy. Setting the PIN code to null will remove it.", + tags: [OpenAPITags.Policy], + request: { + params: setResourcePolicyPincodeParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPincodeBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyPincode( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyRules.ts b/server/routers/policy/setResourcePolicyRules.ts index dfcfb7cdb..4e0ef6e86 100644 --- a/server/routers/policy/setResourcePolicyRules.ts +++ b/server/routers/policy/setResourcePolicyRules.ts @@ -61,6 +61,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/rules", + description: + "Set all rules for a resource policy at once. This will replace all existing rules.", + tags: [OpenAPITags.Policy], + request: { + params: setResourcePolicyRulesParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyRulesBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyRules( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyWhitelist.ts b/server/routers/policy/setResourcePolicyWhitelist.ts index 0bffacec2..f7b6cfc82 100644 --- a/server/routers/policy/setResourcePolicyWhitelist.ts +++ b/server/routers/policy/setResourcePolicyWhitelist.ts @@ -46,6 +46,25 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/whitelist", + description: + "Set email whitelist for a resource policy. This will replace all existing emails.", + tags: [OpenAPITags.Policy], + request: { + params: setResourcePolicyWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyWhitelistBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyWhitelist( req: Request, res: Response, diff --git a/server/routers/policy/updateResourcePolicy.ts b/server/routers/policy/updateResourcePolicy.ts index ad8b19639..29f9112f4 100644 --- a/server/routers/policy/updateResourcePolicy.ts +++ b/server/routers/policy/updateResourcePolicy.ts @@ -36,6 +36,24 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}", + description: "Update a resource policy.", + tags: [OpenAPITags.Org, OpenAPITags.Policy], + request: { + params: updateResourcePolicyParamsSchema, + body: { + content: { + "application/json": { + schema: updateResourcePolicyBodySchema + } + } + } + }, + responses: {} +}); + export async function updateResourcePolicy( req: Request, res: Response, diff --git a/server/routers/resource/addEmailToResourceWhitelist.ts b/server/routers/resource/addEmailToResourceWhitelist.ts index 6d5ef22cd..06227aa47 100644 --- a/server/routers/resource/addEmailToResourceWhitelist.ts +++ b/server/routers/resource/addEmailToResourceWhitelist.ts @@ -63,6 +63,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/whitelist/add", + description: "Add a single email to the resource whitelist.", + tags: [OpenAPITags.PublicResource], + request: { + params: addEmailToResourceWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: addEmailToResourceWhitelistBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addEmailToResourceWhitelist( req: Request, res: Response, diff --git a/server/routers/resource/addRoleToResource.ts b/server/routers/resource/addRoleToResource.ts index a1d493631..fd5867aba 100644 --- a/server/routers/resource/addRoleToResource.ts +++ b/server/routers/resource/addRoleToResource.ts @@ -59,6 +59,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/roles/add", + description: + "Add a single role to a resource. When the resource has an inline policy defined (no shared resource policy assigned), the role is added to the inline policy instead of directly to the resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + request: { + params: addRoleToResourceParamsSchema, + body: { + content: { + "application/json": { + schema: addRoleToResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addRoleToResource( req: Request, res: Response, diff --git a/server/routers/resource/addUserToResource.ts b/server/routers/resource/addUserToResource.ts index ebe43fccd..124645742 100644 --- a/server/routers/resource/addUserToResource.ts +++ b/server/routers/resource/addUserToResource.ts @@ -59,6 +59,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/users/add", + description: + "Add a single user to a resource. When the resource has an inline policy defined (no shared resource policy assigned), the user is added to the inline policy instead of directly to the resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.User], + request: { + params: addUserToResourceParamsSchema, + body: { + content: { + "application/json": { + schema: addUserToResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addUserToResource( req: Request, res: Response, diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 81c5950fb..a7af2d674 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -182,6 +182,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "put", + path: "/org/{orgId}/public-resource", + description: "Create a resource.", + tags: [OpenAPITags.PublicResource], + request: { + params: createResourceParamsSchema, + body: { + content: { + "application/json": { + schema: createHttpResourceSchema.or(createRawResourceSchema) + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function createResource( req: Request, res: Response, diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index 9d2261338..9fb71c29a 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -61,6 +61,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "put", + path: "/public-resource/{resourceId}/rule", + description: "Create a resource rule.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + request: { + params: createResourceRuleParamsSchema, + body: { + content: { + "application/json": { + schema: createResourceRuleSchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function createResourceRule( req: Request, res: Response, diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index d80817360..6398e8b9f 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -44,6 +44,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "delete", + path: "/public-resource/{resourceId}", + description: "Delete a resource.", + tags: [OpenAPITags.PublicResource], + request: { + params: deleteResourceSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function deleteResource( req: Request, res: Response, diff --git a/server/routers/resource/deleteResourceRule.ts b/server/routers/resource/deleteResourceRule.ts index c619a693e..1b9168836 100644 --- a/server/routers/resource/deleteResourceRule.ts +++ b/server/routers/resource/deleteResourceRule.ts @@ -41,6 +41,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "delete", + path: "/public-resource/{resourceId}/rule/{ruleId}", + description: "Delete a resource rule.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + request: { + params: deleteResourceRuleSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function deleteResourceRule( req: Request, res: Response, diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index e139ee58f..45b4dc308 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -116,6 +116,34 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}", + description: "Get a resource by resourceId.", + tags: [OpenAPITags.PublicResource], + request: { + params: z.object({ + resourceId: z.number() + }) + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function getResource( req: Request, res: Response, diff --git a/server/routers/resource/getResourcePolicies.ts b/server/routers/resource/getResourcePolicies.ts index 6742a0bd5..7593473c1 100644 --- a/server/routers/resource/getResourcePolicies.ts +++ b/server/routers/resource/getResourcePolicies.ts @@ -33,6 +33,17 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/policies", + description: "Get the inline and shared policies associated with a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Policy], + request: { + params: getResourcePoliciesParamsSchema + }, + responses: {} +}); + export async function getResourcePolicies( req: Request, res: Response, diff --git a/server/routers/resource/getResourceWhitelist.ts b/server/routers/resource/getResourceWhitelist.ts index 0bebe6a71..4b33d788d 100644 --- a/server/routers/resource/getResourceWhitelist.ts +++ b/server/routers/resource/getResourceWhitelist.ts @@ -66,6 +66,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/whitelist", + description: "Get the whitelist of emails for a specific resource.", + tags: [OpenAPITags.PublicResource], + request: { + params: getResourceWhitelistSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function getResourceWhitelist( req: Request, res: Response, diff --git a/server/routers/resource/listAllResourceNames.ts b/server/routers/resource/listAllResourceNames.ts index e38a3fd69..61878ea2e 100644 --- a/server/routers/resource/listAllResourceNames.ts +++ b/server/routers/resource/listAllResourceNames.ts @@ -57,6 +57,34 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/org/{orgId}/public-resource-names", + description: "List all resource names for an organization.", + tags: [OpenAPITags.PublicResource], + request: { + params: z.object({ + orgId: z.string() + }) + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listAllResourceNames( req: Request, res: Response, diff --git a/server/routers/resource/listResourceRoles.ts b/server/routers/resource/listResourceRoles.ts index ea4d07f64..19879c51c 100644 --- a/server/routers/resource/listResourceRoles.ts +++ b/server/routers/resource/listResourceRoles.ts @@ -70,6 +70,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/roles", + description: "List all roles for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + request: { + params: listResourceRolesSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listResourceRoles( req: Request, res: Response, diff --git a/server/routers/resource/listResourceRules.ts b/server/routers/resource/listResourceRules.ts index ec4cc332e..97edb07c2 100644 --- a/server/routers/resource/listResourceRules.ts +++ b/server/routers/resource/listResourceRules.ts @@ -94,6 +94,33 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/rules", + description: "List rules for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + request: { + params: listResourceRulesParamsSchema, + query: listResourceRulesSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listResourceRules( req: Request, res: Response, diff --git a/server/routers/resource/listResourceUsers.ts b/server/routers/resource/listResourceUsers.ts index c99dbf0ad..5cc756aeb 100644 --- a/server/routers/resource/listResourceUsers.ts +++ b/server/routers/resource/listResourceUsers.ts @@ -60,6 +60,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/users", + description: "List all users for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.User], + request: { + params: listResourceUsersSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listResourceUsers( req: Request, res: Response, diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index e0baa49b1..e8f0f64f9 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -434,6 +434,35 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/org/{orgId}/public-resources", + description: "List resources for an organization.", + tags: [OpenAPITags.PublicResource], + request: { + params: z.object({ + orgId: z.string() + }), + query: listResourcesSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listResources( req: Request, res: Response, diff --git a/server/routers/resource/removeEmailFromResourceWhitelist.ts b/server/routers/resource/removeEmailFromResourceWhitelist.ts index 03d961335..a2e27bc89 100644 --- a/server/routers/resource/removeEmailFromResourceWhitelist.ts +++ b/server/routers/resource/removeEmailFromResourceWhitelist.ts @@ -63,6 +63,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/whitelist/remove", + description: "Remove a single email from the resource whitelist.", + tags: [OpenAPITags.PublicResource], + request: { + params: removeEmailFromResourceWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: removeEmailFromResourceWhitelistBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeEmailFromResourceWhitelist( req: Request, res: Response, diff --git a/server/routers/resource/removeRoleFromResource.ts b/server/routers/resource/removeRoleFromResource.ts index 0c4d811e2..a373ba33e 100644 --- a/server/routers/resource/removeRoleFromResource.ts +++ b/server/routers/resource/removeRoleFromResource.ts @@ -58,6 +58,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/roles/remove", + description: "Remove a single role from a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + request: { + params: removeRoleFromResourceParamsSchema, + body: { + content: { + "application/json": { + schema: removeRoleFromResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeRoleFromResource( req: Request, res: Response, diff --git a/server/routers/resource/removeUserFromResource.ts b/server/routers/resource/removeUserFromResource.ts index 43a22c904..100036497 100644 --- a/server/routers/resource/removeUserFromResource.ts +++ b/server/routers/resource/removeUserFromResource.ts @@ -58,6 +58,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/users/remove", + description: "Remove a single user from a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.User], + request: { + params: removeUserFromResourceParamsSchema, + body: { + content: { + "application/json": { + schema: removeUserFromResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeUserFromResource( req: Request, res: Response, diff --git a/server/routers/resource/setResourceHeaderAuth.ts b/server/routers/resource/setResourceHeaderAuth.ts index 6c5ee1788..8906515d0 100644 --- a/server/routers/resource/setResourceHeaderAuth.ts +++ b/server/routers/resource/setResourceHeaderAuth.ts @@ -60,6 +60,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/header-auth", + description: + "Set or update the header authentication for a resource. If user and password is not provided, it will remove the header authentication.", + tags: [OpenAPITags.PublicResource], + request: { + params: setResourceAuthMethodsParamsSchema, + body: { + content: { + "application/json": { + schema: setResourceAuthMethodsBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourceHeaderAuth( req: Request, res: Response, diff --git a/server/routers/resource/setResourcePassword.ts b/server/routers/resource/setResourcePassword.ts index b52ebfe2c..5e0f7c1bf 100644 --- a/server/routers/resource/setResourcePassword.ts +++ b/server/routers/resource/setResourcePassword.ts @@ -58,6 +58,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/password", + description: + "Set the password for a resource. Setting the password to null will remove it.", + tags: [OpenAPITags.PublicResource], + request: { + params: setResourceAuthMethodsParamsSchema, + body: { + content: { + "application/json": { + schema: setResourceAuthMethodsBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourcePassword( req: Request, res: Response, diff --git a/server/routers/resource/setResourcePincode.ts b/server/routers/resource/setResourcePincode.ts index 577b11dbf..cf8c75ea4 100644 --- a/server/routers/resource/setResourcePincode.ts +++ b/server/routers/resource/setResourcePincode.ts @@ -58,6 +58,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/pincode", + description: + "Set the PIN code for a resource. Setting the PIN code to null will remove it.", + tags: [OpenAPITags.PublicResource], + request: { + params: setResourceAuthMethodsParamsSchema, + body: { + content: { + "application/json": { + schema: setResourceAuthMethodsBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourcePincode( req: Request, res: Response, diff --git a/server/routers/resource/setResourceRoles.ts b/server/routers/resource/setResourceRoles.ts index 681e00650..f640fa5d7 100644 --- a/server/routers/resource/setResourceRoles.ts +++ b/server/routers/resource/setResourceRoles.ts @@ -52,6 +52,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/roles", + description: + "Set roles for a resource. This will replace all existing roles. When the resource has an inline policy defined (no shared resource policy assigned), roles are set on the inline policy instead of directly on the resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + request: { + params: setResourceRolesParamsSchema, + body: { + content: { + "application/json": { + schema: setResourceRolesBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourceRoles( req: Request, res: Response, diff --git a/server/routers/resource/setResourceUsers.ts b/server/routers/resource/setResourceUsers.ts index d292ccba2..4f7877d7c 100644 --- a/server/routers/resource/setResourceUsers.ts +++ b/server/routers/resource/setResourceUsers.ts @@ -52,6 +52,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/users", + description: + "Set users for a resource. This will replace all existing users. When the resource has an inline policy defined (no shared resource policy assigned), users are set on the inline policy instead of directly on the resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.User], + request: { + params: setUserResourcesParamsSchema, + body: { + content: { + "application/json": { + schema: setUserResourcesBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourceUsers( req: Request, res: Response, diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index 10bf5b480..2b883bfad 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -66,6 +66,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/whitelist", + description: + "Set email whitelist for a resource. This will replace all existing emails.", + tags: [OpenAPITags.PublicResource], + request: { + params: setResourceWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: setResourceWhitelistBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setResourceWhitelist( req: Request, res: Response, diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 35995d517..6b055b860 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -273,6 +273,42 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}", + description: + "Update a resource. Policy fields (sso, mfa, pincode, password, whitelist) update the inline policy when no shared resource policy is assigned; when a shared policy is assigned those fields override the shared policy for this resource only.", + tags: [OpenAPITags.PublicResource], + request: { + params: updateResourceParamsSchema, + body: { + content: { + "application/json": { + schema: updateHttpResourceBodySchema.and( + updateRawResourceBodySchema + ) + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function updateResource( req: Request, res: Response, diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index c724d00d2..c9f394730 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -78,6 +78,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/public-resource/{resourceId}/rule/{ruleId}", + description: "Update a resource rule.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + request: { + params: updateResourceRuleParamsSchema, + body: { + content: { + "application/json": { + schema: updateResourceRuleSchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function updateResourceRule( req: Request, res: Response, diff --git a/server/routers/siteResource/addClientToSiteResource.ts b/server/routers/siteResource/addClientToSiteResource.ts index 5e81cf8f7..da585d2c6 100644 --- a/server/routers/siteResource/addClientToSiteResource.ts +++ b/server/routers/siteResource/addClientToSiteResource.ts @@ -62,6 +62,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/clients/add", + description: + "Add a single client to a site resource. Clients with a userId cannot be added.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + request: { + params: addClientToSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: addClientToSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addClientToSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/addRoleToSiteResource.ts b/server/routers/siteResource/addRoleToSiteResource.ts index d3dd164cc..9f4922b63 100644 --- a/server/routers/siteResource/addRoleToSiteResource.ts +++ b/server/routers/siteResource/addRoleToSiteResource.ts @@ -62,6 +62,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/roles/add", + description: "Add a single role to a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + request: { + params: addRoleToSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: addRoleToSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addRoleToSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/addUserToSiteResource.ts b/server/routers/siteResource/addUserToSiteResource.ts index 0a93868ae..83476b189 100644 --- a/server/routers/siteResource/addUserToSiteResource.ts +++ b/server/routers/siteResource/addUserToSiteResource.ts @@ -62,6 +62,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/users/add", + description: "Add a single user to a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + request: { + params: addUserToSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: addUserToSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function addUserToSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/batchAddClientToSiteResources.ts b/server/routers/siteResource/batchAddClientToSiteResources.ts index 3eb282310..558eaee32 100644 --- a/server/routers/siteResource/batchAddClientToSiteResources.ts +++ b/server/routers/siteResource/batchAddClientToSiteResources.ts @@ -67,6 +67,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/client/{clientId}/private-resources", + description: "Add a machine client to multiple site resources at once.", + tags: [OpenAPITags.Client], + request: { + params: batchAddClientToSiteResourcesParamsSchema, + body: { + content: { + "application/json": { + schema: batchAddClientToSiteResourcesBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function batchAddClientToSiteResources( req: Request, res: Response, diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 365053cf1..420c14beb 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -236,6 +236,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "put", + path: "/org/{orgId}/private-resource", + description: "Create a new site resource.", + tags: [OpenAPITags.PrivateResource], + request: { + params: createSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: createSiteResourceSchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function createSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index 63479c399..f84672acd 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -49,6 +49,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "delete", + path: "/private-resource/{siteResourceId}", + description: "Delete a site resource.", + tags: [OpenAPITags.PrivateResource], + request: { + params: deleteSiteResourceParamsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function deleteSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/getSiteResource.ts b/server/routers/siteResource/getSiteResource.ts index f25723d4b..d2fd0385d 100644 --- a/server/routers/siteResource/getSiteResource.ts +++ b/server/routers/siteResource/getSiteResource.ts @@ -83,6 +83,36 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/private-resource/{siteResourceId}", + description: "Get a specific site resource by siteResourceId.", + tags: [OpenAPITags.PrivateResource], + request: { + params: z.object({ + siteResourceId: z.number(), + siteId: z.number(), + orgId: z.string() + }) + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + registry.registerPath({ method: "get", path: "/org/{orgId}/site/{siteId}/resource/nice/{niceId}", diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 2cbbc2c4e..fbfba510e 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -253,6 +253,33 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/org/{orgId}/private-resources", + description: "List all site resources for an organization.", + tags: [OpenAPITags.PrivateResource], + request: { + params: listAllSiteResourcesByOrgParamsSchema, + query: listAllSiteResourcesByOrgQuerySchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listAllSiteResourcesByOrg( req: Request, res: Response, diff --git a/server/routers/siteResource/listSiteResourceClients.ts b/server/routers/siteResource/listSiteResourceClients.ts index 5ae6f6f8d..90255e8cb 100644 --- a/server/routers/siteResource/listSiteResourceClients.ts +++ b/server/routers/siteResource/listSiteResourceClients.ts @@ -61,6 +61,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/private-resource/{siteResourceId}/clients", + description: "List all clients for a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + request: { + params: listSiteResourceClientsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listSiteResourceClients( req: Request, res: Response, diff --git a/server/routers/siteResource/listSiteResourceRoles.ts b/server/routers/siteResource/listSiteResourceRoles.ts index d430c8c43..51a34d7c0 100644 --- a/server/routers/siteResource/listSiteResourceRoles.ts +++ b/server/routers/siteResource/listSiteResourceRoles.ts @@ -62,6 +62,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/private-resource/{siteResourceId}/roles", + description: "List all roles for a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + request: { + params: listSiteResourceRolesSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listSiteResourceRoles( req: Request, res: Response, diff --git a/server/routers/siteResource/listSiteResourceUsers.ts b/server/routers/siteResource/listSiteResourceUsers.ts index d13a58d3b..00f86c588 100644 --- a/server/routers/siteResource/listSiteResourceUsers.ts +++ b/server/routers/siteResource/listSiteResourceUsers.ts @@ -65,6 +65,32 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/private-resource/{siteResourceId}/users", + description: "List all users for a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + request: { + params: listSiteResourceUsersSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listSiteResourceUsers( req: Request, res: Response, diff --git a/server/routers/siteResource/listSiteResources.ts b/server/routers/siteResource/listSiteResources.ts index c97900bec..30d1bece8 100644 --- a/server/routers/siteResource/listSiteResources.ts +++ b/server/routers/siteResource/listSiteResources.ts @@ -90,6 +90,33 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/org/{orgId}/site/{siteId}/private-resources", + description: "List site resources for a site.", + tags: [OpenAPITags.PrivateResource], + request: { + params: listSiteResourcesParamsSchema, + query: listSiteResourcesQuerySchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listSiteResources( req: Request, res: Response, diff --git a/server/routers/siteResource/removeClientFromSiteResource.ts b/server/routers/siteResource/removeClientFromSiteResource.ts index e955dd5c6..26d735c6e 100644 --- a/server/routers/siteResource/removeClientFromSiteResource.ts +++ b/server/routers/siteResource/removeClientFromSiteResource.ts @@ -62,6 +62,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/clients/remove", + description: + "Remove a single client from a site resource. Clients with a userId cannot be removed.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + request: { + params: removeClientFromSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: removeClientFromSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeClientFromSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/removeRoleFromSiteResource.ts b/server/routers/siteResource/removeRoleFromSiteResource.ts index 509088597..872452e48 100644 --- a/server/routers/siteResource/removeRoleFromSiteResource.ts +++ b/server/routers/siteResource/removeRoleFromSiteResource.ts @@ -62,6 +62,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/roles/remove", + description: "Remove a single role from a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + request: { + params: removeRoleFromSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: removeRoleFromSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeRoleFromSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/removeUserFromSiteResource.ts b/server/routers/siteResource/removeUserFromSiteResource.ts index 3d8e1fd97..6ad275508 100644 --- a/server/routers/siteResource/removeUserFromSiteResource.ts +++ b/server/routers/siteResource/removeUserFromSiteResource.ts @@ -62,6 +62,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/users/remove", + description: "Remove a single user from a site resource.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + request: { + params: removeUserFromSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: removeUserFromSiteResourceBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function removeUserFromSiteResource( req: Request, res: Response, diff --git a/server/routers/siteResource/setSiteResourceClients.ts b/server/routers/siteResource/setSiteResourceClients.ts index 59dff6ef9..43f61666f 100644 --- a/server/routers/siteResource/setSiteResourceClients.ts +++ b/server/routers/siteResource/setSiteResourceClients.ts @@ -62,6 +62,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/clients", + description: + "Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + request: { + params: setSiteResourceClientsParamsSchema, + body: { + content: { + "application/json": { + schema: setSiteResourceClientsBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setSiteResourceClients( req: Request, res: Response, diff --git a/server/routers/siteResource/setSiteResourceRoles.ts b/server/routers/siteResource/setSiteResourceRoles.ts index 277ef437e..cc733cc8e 100644 --- a/server/routers/siteResource/setSiteResourceRoles.ts +++ b/server/routers/siteResource/setSiteResourceRoles.ts @@ -63,6 +63,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/roles", + description: + "Set roles for a site resource. This will replace all existing roles.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + request: { + params: setSiteResourceRolesParamsSchema, + body: { + content: { + "application/json": { + schema: setSiteResourceRolesBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setSiteResourceRoles( req: Request, res: Response, diff --git a/server/routers/siteResource/setSiteResourceUsers.ts b/server/routers/siteResource/setSiteResourceUsers.ts index 4a423bf06..0ad912831 100644 --- a/server/routers/siteResource/setSiteResourceUsers.ts +++ b/server/routers/siteResource/setSiteResourceUsers.ts @@ -64,6 +64,40 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}/users", + description: + "Set users for a site resource. This will replace all existing users.", + tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + request: { + params: setSiteResourceUsersParamsSchema, + body: { + content: { + "application/json": { + schema: setSiteResourceUsersBodySchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function setSiteResourceUsers( req: Request, res: Response, diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 0ccf29908..6c3c23e3f 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -242,6 +242,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "post", + path: "/private-resource/{siteResourceId}", + description: "Update a site resource.", + tags: [OpenAPITags.PrivateResource], + request: { + params: updateSiteResourceParamsSchema, + body: { + content: { + "application/json": { + schema: updateSiteResourceSchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function updateSiteResource( req: Request, res: Response, diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 289f47c76..8d4ed3696 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -122,6 +122,39 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "put", + path: "/public-resource/{resourceId}/target", + description: "Create a target for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Target], + request: { + params: createTargetParamsSchema, + body: { + content: { + "application/json": { + schema: createTargetSchema + } + } + } + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function createTarget( req: Request, res: Response, diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index 68f80197a..b22476023 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -115,6 +115,33 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "get", + path: "/public-resource/{resourceId}/targets", + description: "List targets for a resource.", + tags: [OpenAPITags.PublicResource, OpenAPITags.Target], + request: { + params: listTargetsParamsSchema, + query: listTargetsSchema + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: z.object({ + data: z.record(z.string(), z.any()).nullable(), + success: z.boolean(), + error: z.boolean(), + message: z.string(), + status: z.number() + }) + } + } + } + } +}); + export async function listTargets( req: Request, res: Response, From 17e03457e1ead9ab68ac28b97f2fe6cd0e85f8c4 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 15:52:37 -0400 Subject: [PATCH 85/94] Recategorize --- server/openApi.ts | 2 ++ .../routers/resource/addEmailToResourceWhitelist.ts | 7 ++----- server/routers/resource/createResource.ts | 2 +- server/routers/resource/deleteResource.ts | 2 +- server/routers/resource/getResource.ts | 4 ++-- server/routers/resource/getResourceWhitelist.ts | 2 +- server/routers/resource/listAllResourceNames.ts | 2 +- server/routers/resource/listResources.ts | 2 +- .../resource/removeEmailFromResourceWhitelist.ts | 12 +++--------- server/routers/resource/setResourceHeaderAuth.ts | 2 +- server/routers/resource/setResourcePassword.ts | 2 +- server/routers/resource/setResourcePincode.ts | 2 +- server/routers/resource/setResourceWhitelist.ts | 2 +- server/routers/resource/updateResource.ts | 2 +- server/routers/siteResource/createSiteResource.ts | 2 +- server/routers/siteResource/deleteSiteResource.ts | 2 +- server/routers/siteResource/getSiteResource.ts | 2 +- .../siteResource/listAllSiteResourcesByOrg.ts | 2 +- server/routers/siteResource/listSiteResources.ts | 2 +- server/routers/siteResource/updateSiteResource.ts | 2 +- 20 files changed, 25 insertions(+), 32 deletions(-) diff --git a/server/openApi.ts b/server/openApi.ts index 63625192d..cb9ad5dc8 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -6,7 +6,9 @@ export enum OpenAPITags { Site = "Site", Org = "Organization", PublicResource = "Public Resource", + PublicResourceLegacy = "Public Resource (Legacy)", PrivateResource = "Private Resource", + PrivateResourceLegacy = "Private Resource (Legacy)", Policy = "Policy", Role = "Role", User = "User", diff --git a/server/routers/resource/addEmailToResourceWhitelist.ts b/server/routers/resource/addEmailToResourceWhitelist.ts index 06227aa47..97eee29b8 100644 --- a/server/routers/resource/addEmailToResourceWhitelist.ts +++ b/server/routers/resource/addEmailToResourceWhitelist.ts @@ -34,7 +34,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/whitelist/add", description: "Add a single email to the resource whitelist.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: addEmailToResourceWhitelistParamsSchema, body: { @@ -177,10 +177,7 @@ export async function addEmailToResourceWhitelist( .from(resourcePolicyWhiteList) .where( and( - eq( - resourcePolicyWhiteList.resourcePolicyId, - policyId - ), + eq(resourcePolicyWhiteList.resourcePolicyId, policyId), eq(resourcePolicyWhiteList.email, email) ) ); diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index a7af2d674..ab758cc1c 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -153,7 +153,7 @@ registry.registerPath({ method: "put", path: "/org/{orgId}/resource", description: "Create a resource.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: createResourceParamsSchema, body: { diff --git a/server/routers/resource/deleteResource.ts b/server/routers/resource/deleteResource.ts index 6398e8b9f..5d42761de 100644 --- a/server/routers/resource/deleteResource.ts +++ b/server/routers/resource/deleteResource.ts @@ -22,7 +22,7 @@ registry.registerPath({ method: "delete", path: "/resource/{resourceId}", description: "Delete a resource.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: deleteResourceSchema }, diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index 45b4dc308..b1f4108b3 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -63,7 +63,7 @@ registry.registerPath({ path: "/org/{orgId}/resource/{niceId}", description: "Get a resource by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: z.object({ orgId: z.string(), @@ -92,7 +92,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}", description: "Get a resource by resourceId.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: z.object({ resourceId: z.number() diff --git a/server/routers/resource/getResourceWhitelist.ts b/server/routers/resource/getResourceWhitelist.ts index 4b33d788d..ca8b5d9ee 100644 --- a/server/routers/resource/getResourceWhitelist.ts +++ b/server/routers/resource/getResourceWhitelist.ts @@ -44,7 +44,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/whitelist", description: "Get the whitelist of emails for a specific resource.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: getResourceWhitelistSchema }, diff --git a/server/routers/resource/listAllResourceNames.ts b/server/routers/resource/listAllResourceNames.ts index 61878ea2e..ba52a2f88 100644 --- a/server/routers/resource/listAllResourceNames.ts +++ b/server/routers/resource/listAllResourceNames.ts @@ -33,7 +33,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/resources-names", description: "List all resource names for an organization.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: z.object({ orgId: z.string() diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index e8f0f64f9..cc075f23b 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -409,7 +409,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/resources", description: "List resources for an organization.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: z.object({ orgId: z.string() diff --git a/server/routers/resource/removeEmailFromResourceWhitelist.ts b/server/routers/resource/removeEmailFromResourceWhitelist.ts index a2e27bc89..58631a5f3 100644 --- a/server/routers/resource/removeEmailFromResourceWhitelist.ts +++ b/server/routers/resource/removeEmailFromResourceWhitelist.ts @@ -34,7 +34,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/whitelist/remove", description: "Remove a single email from the resource whitelist.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: removeEmailFromResourceWhitelistParamsSchema, body: { @@ -176,10 +176,7 @@ export async function removeEmailFromResourceWhitelist( .from(resourcePolicyWhiteList) .where( and( - eq( - resourcePolicyWhiteList.resourcePolicyId, - policyId - ), + eq(resourcePolicyWhiteList.resourcePolicyId, policyId), eq(resourcePolicyWhiteList.email, email) ) ); @@ -197,10 +194,7 @@ export async function removeEmailFromResourceWhitelist( .delete(resourcePolicyWhiteList) .where( and( - eq( - resourcePolicyWhiteList.resourcePolicyId, - policyId - ), + eq(resourcePolicyWhiteList.resourcePolicyId, policyId), eq(resourcePolicyWhiteList.email, email) ) ); diff --git a/server/routers/resource/setResourceHeaderAuth.ts b/server/routers/resource/setResourceHeaderAuth.ts index 8906515d0..c618a2902 100644 --- a/server/routers/resource/setResourceHeaderAuth.ts +++ b/server/routers/resource/setResourceHeaderAuth.ts @@ -31,7 +31,7 @@ registry.registerPath({ path: "/resource/{resourceId}/header-auth", description: "Set or update the header authentication for a resource. If user and password is not provided, it will remove the header authentication.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setResourceAuthMethodsParamsSchema, body: { diff --git a/server/routers/resource/setResourcePassword.ts b/server/routers/resource/setResourcePassword.ts index 5e0f7c1bf..8a74823a9 100644 --- a/server/routers/resource/setResourcePassword.ts +++ b/server/routers/resource/setResourcePassword.ts @@ -29,7 +29,7 @@ registry.registerPath({ path: "/resource/{resourceId}/password", description: "Set the password for a resource. Setting the password to null will remove it.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setResourceAuthMethodsParamsSchema, body: { diff --git a/server/routers/resource/setResourcePincode.ts b/server/routers/resource/setResourcePincode.ts index cf8c75ea4..033b226e1 100644 --- a/server/routers/resource/setResourcePincode.ts +++ b/server/routers/resource/setResourcePincode.ts @@ -29,7 +29,7 @@ registry.registerPath({ path: "/resource/{resourceId}/pincode", description: "Set the PIN code for a resource. Setting the PIN code to null will remove it.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setResourceAuthMethodsParamsSchema, body: { diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index 2b883bfad..e6df96de8 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -37,7 +37,7 @@ registry.registerPath({ path: "/resource/{resourceId}/whitelist", description: "Set email whitelist for a resource. This will replace all existing emails.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setResourceWhitelistParamsSchema, body: { diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 6b055b860..f3e0c3909 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -242,7 +242,7 @@ registry.registerPath({ path: "/resource/{resourceId}", description: "Update a resource. Policy fields (sso, mfa, pincode, password, whitelist) update the inline policy when no shared resource policy is assigned; when a shared policy is assigned those fields override the shared policy for this resource only.", - tags: [OpenAPITags.PublicResource], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: updateResourceParamsSchema, body: { diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 420c14beb..d3e32babd 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -207,7 +207,7 @@ registry.registerPath({ method: "put", path: "/org/{orgId}/site-resource", description: "Create a new site resource.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: createSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/deleteSiteResource.ts b/server/routers/siteResource/deleteSiteResource.ts index f84672acd..cdb438736 100644 --- a/server/routers/siteResource/deleteSiteResource.ts +++ b/server/routers/siteResource/deleteSiteResource.ts @@ -27,7 +27,7 @@ registry.registerPath({ method: "delete", path: "/site-resource/{siteResourceId}", description: "Delete a site resource.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: deleteSiteResourceParamsSchema }, diff --git a/server/routers/siteResource/getSiteResource.ts b/server/routers/siteResource/getSiteResource.ts index d2fd0385d..47a89d1b1 100644 --- a/server/routers/siteResource/getSiteResource.ts +++ b/server/routers/siteResource/getSiteResource.ts @@ -57,7 +57,7 @@ registry.registerPath({ method: "get", path: "/site-resource/{siteResourceId}", description: "Get a specific site resource by siteResourceId.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: z.object({ siteResourceId: z.number(), diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index fbfba510e..50acbeb4f 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -230,7 +230,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/site-resources", description: "List all site resources for an organization.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: listAllSiteResourcesByOrgParamsSchema, query: listAllSiteResourcesByOrgQuerySchema diff --git a/server/routers/siteResource/listSiteResources.ts b/server/routers/siteResource/listSiteResources.ts index 30d1bece8..da0ba9a49 100644 --- a/server/routers/siteResource/listSiteResources.ts +++ b/server/routers/siteResource/listSiteResources.ts @@ -67,7 +67,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/site/{siteId}/resources", description: "List site resources for a site.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: listSiteResourcesParamsSchema, query: listSiteResourcesQuerySchema diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index 6c3c23e3f..7b0ef0783 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -213,7 +213,7 @@ registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}", description: "Update a site resource.", - tags: [OpenAPITags.PrivateResource], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: updateSiteResourceParamsSchema, body: { From 70e2fe1e4e632145082141f67210234f71fb0351 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 16:01:22 -0400 Subject: [PATCH 86/94] Just add ordering --- server/integrationApiServer.ts | 5 +++-- server/openApi.ts | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/server/integrationApiServer.ts b/server/integrationApiServer.ts index 104e02d5c..56a3d47b0 100644 --- a/server/integrationApiServer.ts +++ b/server/integrationApiServer.ts @@ -12,7 +12,7 @@ import { logIncomingMiddleware } from "./middlewares/logIncoming"; import helmet from "helmet"; import swaggerUi from "swagger-ui-express"; import { OpenApiGeneratorV3 } from "@asteasolutions/zod-to-openapi"; -import { registry } from "./openApi"; +import { registry, openApiTags } from "./openApi"; import fs from "fs"; import path from "path"; import { APP_PATH } from "./lib/consts"; @@ -181,7 +181,8 @@ function getOpenApiDocumentation() { version: "v1", title: "Pangolin Integration API" }, - servers: [{ url: "/v1" }] + servers: [{ url: "/v1" }], + tags: openApiTags }); if (!process.env.DISABLE_GEN_OPENAPI) { diff --git a/server/openApi.ts b/server/openApi.ts index cb9ad5dc8..935b64f8d 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -4,25 +4,30 @@ export const registry = new OpenAPIRegistry(); export enum OpenAPITags { Site = "Site", - Org = "Organization", PublicResource = "Public Resource", - PublicResourceLegacy = "Public Resource (Legacy)", + Target = "Resource Target", PrivateResource = "Private Resource", - PrivateResourceLegacy = "Private Resource (Legacy)", + Client = "Client", + Org = "Organization", + Domain = "Domain", Policy = "Policy", Role = "Role", User = "User", - Invitation = "User Invitation", - Target = "Resource Target", Rule = "Rule", + Invitation = "User Invitation", AccessToken = "Access Token", GlobalIdp = "Identity Provider (Global)", OrgIdp = "Identity Provider (Organization Only)", - Client = "Client", ApiKey = "API Key", SiteProvisioningKey = "Site Provisioning Key", - Domain = "Domain", Blueprint = "Blueprint", Ssh = "SSH", - Logs = "Logs" + Logs = "Logs", + PublicResourceLegacy = "Public Resource (Legacy)", + PrivateResourceLegacy = "Private Resource (Legacy)" } + +// Order here controls the order tags are displayed in Swagger UI +export const openApiTags = Object.values(OpenAPITags).map((name) => ({ + name +})); From 75afe6ece2ecabb015081b3ecaa4bdc93fd06a78 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 16:46:16 -0400 Subject: [PATCH 87/94] Order more api routes --- server/routers/accessToken/generateAccessToken.ts | 2 +- server/routers/accessToken/listAccessTokens.ts | 2 +- server/routers/resource/addRoleToResource.ts | 2 +- server/routers/resource/addUserToResource.ts | 2 +- server/routers/resource/createResourceRule.ts | 2 +- server/routers/resource/deleteResourceRule.ts | 2 +- server/routers/resource/getResourcePolicies.ts | 8 +++++--- server/routers/resource/listResourceRoles.ts | 2 +- server/routers/resource/listResourceRules.ts | 2 +- server/routers/resource/listResourceUsers.ts | 2 +- server/routers/resource/removeRoleFromResource.ts | 2 +- server/routers/resource/removeUserFromResource.ts | 2 +- server/routers/resource/setResourceRoles.ts | 2 +- server/routers/resource/setResourceUsers.ts | 2 +- server/routers/resource/updateResourceRule.ts | 2 +- server/routers/target/createTarget.ts | 2 +- server/routers/target/listTargets.ts | 2 +- 17 files changed, 21 insertions(+), 19 deletions(-) diff --git a/server/routers/accessToken/generateAccessToken.ts b/server/routers/accessToken/generateAccessToken.ts index c46e94162..79eecc59a 100644 --- a/server/routers/accessToken/generateAccessToken.ts +++ b/server/routers/accessToken/generateAccessToken.ts @@ -45,7 +45,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/access-token", description: "Generate a new access token for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: generateAccssTokenParamsSchema, body: { diff --git a/server/routers/accessToken/listAccessTokens.ts b/server/routers/accessToken/listAccessTokens.ts index 0068e459a..9a8796e0b 100644 --- a/server/routers/accessToken/listAccessTokens.ts +++ b/server/routers/accessToken/listAccessTokens.ts @@ -159,7 +159,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/access-tokens", description: "List all access tokens for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.AccessToken], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: z.object({ resourceId: z.number() diff --git a/server/routers/resource/addRoleToResource.ts b/server/routers/resource/addRoleToResource.ts index fd5867aba..1fac3b7db 100644 --- a/server/routers/resource/addRoleToResource.ts +++ b/server/routers/resource/addRoleToResource.ts @@ -30,7 +30,7 @@ registry.registerPath({ path: "/resource/{resourceId}/roles/add", description: "Add a single role to a resource. When the resource has an inline policy defined (no shared resource policy assigned), the role is added to the inline policy instead of directly to the resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + tags: [OpenAPITags.PublicResourceLegacy, OpenAPITags.Role], request: { params: addRoleToResourceParamsSchema, body: { diff --git a/server/routers/resource/addUserToResource.ts b/server/routers/resource/addUserToResource.ts index 124645742..e43a1e324 100644 --- a/server/routers/resource/addUserToResource.ts +++ b/server/routers/resource/addUserToResource.ts @@ -30,7 +30,7 @@ registry.registerPath({ path: "/resource/{resourceId}/users/add", description: "Add a single user to a resource. When the resource has an inline policy defined (no shared resource policy assigned), the user is added to the inline policy instead of directly to the resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.User], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: addUserToResourceParamsSchema, body: { diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index 9fb71c29a..f75922722 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -32,7 +32,7 @@ registry.registerPath({ method: "put", path: "/resource/{resourceId}/rule", description: "Create a resource rule.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: createResourceRuleParamsSchema, body: { diff --git a/server/routers/resource/deleteResourceRule.ts b/server/routers/resource/deleteResourceRule.ts index 1b9168836..d7b7c2a65 100644 --- a/server/routers/resource/deleteResourceRule.ts +++ b/server/routers/resource/deleteResourceRule.ts @@ -19,7 +19,7 @@ registry.registerPath({ method: "delete", path: "/resource/{resourceId}/rule/{ruleId}", description: "Delete a resource rule.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: deleteResourceRuleSchema }, diff --git a/server/routers/resource/getResourcePolicies.ts b/server/routers/resource/getResourcePolicies.ts index 7593473c1..30c9e95fc 100644 --- a/server/routers/resource/getResourcePolicies.ts +++ b/server/routers/resource/getResourcePolicies.ts @@ -25,8 +25,9 @@ export type GetResourcePoliciesResponse = { registry.registerPath({ method: "get", path: "/resource/{resourceId}/policies", - description: "Get the inline and shared policies associated with a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Policy], + description: + "Get the inline and shared policies associated with a resource.", + tags: [OpenAPITags.PublicResourceLegacy], request: { params: getResourcePoliciesParamsSchema }, @@ -36,7 +37,8 @@ registry.registerPath({ registry.registerPath({ method: "get", path: "/public-resource/{resourceId}/policies", - description: "Get the inline and shared policies associated with a resource.", + description: + "Get the inline and shared policies associated with a resource.", tags: [OpenAPITags.PublicResource, OpenAPITags.Policy], request: { params: getResourcePoliciesParamsSchema diff --git a/server/routers/resource/listResourceRoles.ts b/server/routers/resource/listResourceRoles.ts index 19879c51c..f5b84cbf9 100644 --- a/server/routers/resource/listResourceRoles.ts +++ b/server/routers/resource/listResourceRoles.ts @@ -48,7 +48,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/roles", description: "List all roles for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: listResourceRolesSchema }, diff --git a/server/routers/resource/listResourceRules.ts b/server/routers/resource/listResourceRules.ts index 97edb07c2..6b32f82bf 100644 --- a/server/routers/resource/listResourceRules.ts +++ b/server/routers/resource/listResourceRules.ts @@ -71,7 +71,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/rules", description: "List rules for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: listResourceRulesParamsSchema, query: listResourceRulesSchema diff --git a/server/routers/resource/listResourceUsers.ts b/server/routers/resource/listResourceUsers.ts index 5cc756aeb..afabd3052 100644 --- a/server/routers/resource/listResourceUsers.ts +++ b/server/routers/resource/listResourceUsers.ts @@ -38,7 +38,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/users", description: "List all users for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.User], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: listResourceUsersSchema }, diff --git a/server/routers/resource/removeRoleFromResource.ts b/server/routers/resource/removeRoleFromResource.ts index a373ba33e..4c91773f1 100644 --- a/server/routers/resource/removeRoleFromResource.ts +++ b/server/routers/resource/removeRoleFromResource.ts @@ -29,7 +29,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/roles/remove", description: "Remove a single role from a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: removeRoleFromResourceParamsSchema, body: { diff --git a/server/routers/resource/removeUserFromResource.ts b/server/routers/resource/removeUserFromResource.ts index 100036497..d48f31983 100644 --- a/server/routers/resource/removeUserFromResource.ts +++ b/server/routers/resource/removeUserFromResource.ts @@ -29,7 +29,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/users/remove", description: "Remove a single user from a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.User], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: removeUserFromResourceParamsSchema, body: { diff --git a/server/routers/resource/setResourceRoles.ts b/server/routers/resource/setResourceRoles.ts index f640fa5d7..d21cca478 100644 --- a/server/routers/resource/setResourceRoles.ts +++ b/server/routers/resource/setResourceRoles.ts @@ -23,7 +23,7 @@ registry.registerPath({ path: "/resource/{resourceId}/roles", description: "Set roles for a resource. This will replace all existing roles. When the resource has an inline policy defined (no shared resource policy assigned), roles are set on the inline policy instead of directly on the resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Role], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setResourceRolesParamsSchema, body: { diff --git a/server/routers/resource/setResourceUsers.ts b/server/routers/resource/setResourceUsers.ts index 4f7877d7c..0ae833071 100644 --- a/server/routers/resource/setResourceUsers.ts +++ b/server/routers/resource/setResourceUsers.ts @@ -23,7 +23,7 @@ registry.registerPath({ path: "/resource/{resourceId}/users", description: "Set users for a resource. This will replace all existing users. When the resource has an inline policy defined (no shared resource policy assigned), users are set on the inline policy instead of directly on the resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.User], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: setUserResourcesParamsSchema, body: { diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index c9f394730..0ca574017 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -49,7 +49,7 @@ registry.registerPath({ method: "post", path: "/resource/{resourceId}/rule/{ruleId}", description: "Update a resource rule.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Rule], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: updateResourceRuleParamsSchema, body: { diff --git a/server/routers/target/createTarget.ts b/server/routers/target/createTarget.ts index 8d4ed3696..cf2cd6b5f 100644 --- a/server/routers/target/createTarget.ts +++ b/server/routers/target/createTarget.ts @@ -93,7 +93,7 @@ registry.registerPath({ method: "put", path: "/resource/{resourceId}/target", description: "Create a target for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Target], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: createTargetParamsSchema, body: { diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index b22476023..f2d16dd21 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -92,7 +92,7 @@ registry.registerPath({ method: "get", path: "/resource/{resourceId}/targets", description: "List targets for a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Target], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: listTargetsParamsSchema, query: listTargetsSchema From 0b82dae01eb5c891300382890073ccd38160b7e2 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 16:54:00 -0400 Subject: [PATCH 88/94] More route cleanup --- server/openApi.ts | 3 ++- server/private/routers/policy/createResourcePolicy.ts | 2 +- server/private/routers/policy/deleteResourcePolicy.ts | 2 +- server/private/routers/policy/listResourcePolicies.ts | 2 +- server/routers/policy/getResourcePolicy.ts | 6 +++--- server/routers/policy/setResourcePolicyAccessControl.ts | 4 ++-- server/routers/policy/setResourcePolicyHeaderAuth.ts | 4 ++-- server/routers/policy/setResourcePolicyPassword.ts | 4 ++-- server/routers/policy/setResourcePolicyPincode.ts | 4 ++-- server/routers/policy/setResourcePolicyRules.ts | 4 ++-- server/routers/policy/setResourcePolicyWhitelist.ts | 4 ++-- server/routers/policy/updateResourcePolicy.ts | 4 ++-- server/routers/resource/getResourcePolicies.ts | 2 +- server/routers/siteResource/addClientToSiteResource.ts | 2 +- server/routers/siteResource/addRoleToSiteResource.ts | 2 +- server/routers/siteResource/addUserToSiteResource.ts | 2 +- server/routers/siteResource/listSiteResourceClients.ts | 2 +- server/routers/siteResource/listSiteResourceRoles.ts | 2 +- server/routers/siteResource/listSiteResourceUsers.ts | 2 +- server/routers/siteResource/removeClientFromSiteResource.ts | 2 +- server/routers/siteResource/removeRoleFromSiteResource.ts | 2 +- server/routers/siteResource/removeUserFromSiteResource.ts | 2 +- server/routers/siteResource/setSiteResourceClients.ts | 2 +- server/routers/siteResource/setSiteResourceRoles.ts | 2 +- server/routers/siteResource/setSiteResourceUsers.ts | 2 +- 25 files changed, 35 insertions(+), 34 deletions(-) diff --git a/server/openApi.ts b/server/openApi.ts index 935b64f8d..f80165d9e 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -10,7 +10,7 @@ export enum OpenAPITags { Client = "Client", Org = "Organization", Domain = "Domain", - Policy = "Policy", + PublicResourcePolicy = "Public Resource Policy", Role = "Role", User = "User", Rule = "Rule", @@ -23,6 +23,7 @@ export enum OpenAPITags { Blueprint = "Blueprint", Ssh = "SSH", Logs = "Logs", + PublicResourcePolicyLegacy = "Public Resource Policy (Legacy)", PublicResourceLegacy = "Public Resource (Legacy)", PrivateResourceLegacy = "Private Resource (Legacy)" } diff --git a/server/private/routers/policy/createResourcePolicy.ts b/server/private/routers/policy/createResourcePolicy.ts index 9f02b912c..5b8016286 100644 --- a/server/private/routers/policy/createResourcePolicy.ts +++ b/server/private/routers/policy/createResourcePolicy.ts @@ -121,7 +121,7 @@ registry.registerPath({ method: "post", path: "/org/{orgId}/resource-policy", description: "Create a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.Policy], + tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], request: { params: createResourcePolicyParamsSchema, body: { diff --git a/server/private/routers/policy/deleteResourcePolicy.ts b/server/private/routers/policy/deleteResourcePolicy.ts index a586cf3b4..6f14f1cde 100644 --- a/server/private/routers/policy/deleteResourcePolicy.ts +++ b/server/private/routers/policy/deleteResourcePolicy.ts @@ -31,7 +31,7 @@ registry.registerPath({ method: "delete", path: "/resource-policy/{resourcePolicyId}", description: "Delete a resource policy.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: deleteResourcePolicySchema }, diff --git a/server/private/routers/policy/listResourcePolicies.ts b/server/private/routers/policy/listResourcePolicies.ts index a5a52d9c8..7bd17a5f5 100644 --- a/server/private/routers/policy/listResourcePolicies.ts +++ b/server/private/routers/policy/listResourcePolicies.ts @@ -79,7 +79,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/resource-policies", description: "List resource policies for an organization.", - tags: [OpenAPITags.Org, OpenAPITags.Policy], + tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], request: { params: z.object({ orgId: z.string() diff --git a/server/routers/policy/getResourcePolicy.ts b/server/routers/policy/getResourcePolicy.ts index fa9102082..4ce032b0b 100644 --- a/server/routers/policy/getResourcePolicy.ts +++ b/server/routers/policy/getResourcePolicy.ts @@ -168,7 +168,7 @@ registry.registerPath({ path: "/org/{orgId}/resource-policy/{niceId}", description: "Get a resource policy by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.", - tags: [OpenAPITags.Org, OpenAPITags.Policy], + tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], request: { params: z.object({ orgId: z.string(), @@ -182,7 +182,7 @@ registry.registerPath({ method: "get", path: "/resource-policy/{resourcePolicyId}", description: "Get a resource policy by its resourcePolicyId.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: z.object({ resourcePolicyId: z.number() @@ -195,7 +195,7 @@ registry.registerPath({ method: "get", path: "/public-resource-policy/{resourcePolicyId}", description: "Get a resource policy by its resourcePolicyId.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: z.object({ resourcePolicyId: z.number() diff --git a/server/routers/policy/setResourcePolicyAccessControl.ts b/server/routers/policy/setResourcePolicyAccessControl.ts index c59390df1..c74a01410 100644 --- a/server/routers/policy/setResourcePolicyAccessControl.ts +++ b/server/routers/policy/setResourcePolicyAccessControl.ts @@ -40,7 +40,7 @@ registry.registerPath({ path: "/resource-policy/{resourceId}/access-control", description: "Set access control users for a resource policy, including SSO, users, roles, Identity provider.", - tags: [OpenAPITags.Policy, OpenAPITags.User], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyAccessControlParamsSchema, body: { @@ -59,7 +59,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourceId}/access-control", description: "Set access control users for a resource policy, including SSO, users, roles, Identity provider.", - tags: [OpenAPITags.Policy, OpenAPITags.User], + tags: [OpenAPITags.PublicResourcePolicy, OpenAPITags.User], request: { params: setResourcePolicyAccessControlParamsSchema, body: { diff --git a/server/routers/policy/setResourcePolicyHeaderAuth.ts b/server/routers/policy/setResourcePolicyHeaderAuth.ts index d47dd7239..efbbedf8a 100644 --- a/server/routers/policy/setResourcePolicyHeaderAuth.ts +++ b/server/routers/policy/setResourcePolicyHeaderAuth.ts @@ -29,7 +29,7 @@ registry.registerPath({ path: "/resource-policy/{resourcePolicyId}/header-auth", description: "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyHeaderAuthParamsSchema, body: { @@ -48,7 +48,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourcePolicyId}/header-auth", description: "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: setResourcePolicyHeaderAuthParamsSchema, body: { diff --git a/server/routers/policy/setResourcePolicyPassword.ts b/server/routers/policy/setResourcePolicyPassword.ts index 1ab2e549a..909df6da0 100644 --- a/server/routers/policy/setResourcePolicyPassword.ts +++ b/server/routers/policy/setResourcePolicyPassword.ts @@ -24,7 +24,7 @@ registry.registerPath({ path: "/resource-policy/{resourcePolicyId}/password", description: "Set the password for a resource policy. Setting the password to null will remove it.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyPasswordParamsSchema, body: { @@ -43,7 +43,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourcePolicyId}/password", description: "Set the password for a resource policy. Setting the password to null will remove it.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: setResourcePolicyPasswordParamsSchema, body: { diff --git a/server/routers/policy/setResourcePolicyPincode.ts b/server/routers/policy/setResourcePolicyPincode.ts index c6849cc87..73fc26090 100644 --- a/server/routers/policy/setResourcePolicyPincode.ts +++ b/server/routers/policy/setResourcePolicyPincode.ts @@ -27,7 +27,7 @@ registry.registerPath({ path: "/resource-policy/{resourcePolicyId}/pincode", description: "Set the PIN code for a resource policy. Setting the PIN code to null will remove it.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyPincodeParamsSchema, body: { @@ -46,7 +46,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourcePolicyId}/pincode", description: "Set the PIN code for a resource policy. Setting the PIN code to null will remove it.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: setResourcePolicyPincodeParamsSchema, body: { diff --git a/server/routers/policy/setResourcePolicyRules.ts b/server/routers/policy/setResourcePolicyRules.ts index 4e0ef6e86..9499afa6e 100644 --- a/server/routers/policy/setResourcePolicyRules.ts +++ b/server/routers/policy/setResourcePolicyRules.ts @@ -47,7 +47,7 @@ registry.registerPath({ path: "/resource-policy/{resourcePolicyId}/rules", description: "Set all rules for a resource policy at once. This will replace all existing rules.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyRulesParamsSchema, body: { @@ -66,7 +66,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourcePolicyId}/rules", description: "Set all rules for a resource policy at once. This will replace all existing rules.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: setResourcePolicyRulesParamsSchema, body: { diff --git a/server/routers/policy/setResourcePolicyWhitelist.ts b/server/routers/policy/setResourcePolicyWhitelist.ts index f7b6cfc82..788419533 100644 --- a/server/routers/policy/setResourcePolicyWhitelist.ts +++ b/server/routers/policy/setResourcePolicyWhitelist.ts @@ -32,7 +32,7 @@ registry.registerPath({ path: "/resource-policy/{resourcePolicyId}/whitelist", description: "Set email whitelist for a resource policy. This will replace all existing emails.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicyLegacy], request: { params: setResourcePolicyWhitelistParamsSchema, body: { @@ -51,7 +51,7 @@ registry.registerPath({ path: "/public-resource-policy/{resourcePolicyId}/whitelist", description: "Set email whitelist for a resource policy. This will replace all existing emails.", - tags: [OpenAPITags.Policy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: setResourcePolicyWhitelistParamsSchema, body: { diff --git a/server/routers/policy/updateResourcePolicy.ts b/server/routers/policy/updateResourcePolicy.ts index 29f9112f4..7e4d6c250 100644 --- a/server/routers/policy/updateResourcePolicy.ts +++ b/server/routers/policy/updateResourcePolicy.ts @@ -22,7 +22,7 @@ registry.registerPath({ method: "put", path: "/resource-policy/{resourcePolicyId}", description: "Update a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.Policy], + tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], request: { params: updateResourcePolicyParamsSchema, body: { @@ -40,7 +40,7 @@ registry.registerPath({ method: "put", path: "/public-resource-policy/{resourcePolicyId}", description: "Update a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.Policy], + tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], request: { params: updateResourcePolicyParamsSchema, body: { diff --git a/server/routers/resource/getResourcePolicies.ts b/server/routers/resource/getResourcePolicies.ts index 30c9e95fc..b87c6caf6 100644 --- a/server/routers/resource/getResourcePolicies.ts +++ b/server/routers/resource/getResourcePolicies.ts @@ -39,7 +39,7 @@ registry.registerPath({ path: "/public-resource/{resourceId}/policies", description: "Get the inline and shared policies associated with a resource.", - tags: [OpenAPITags.PublicResource, OpenAPITags.Policy], + tags: [OpenAPITags.PublicResource, OpenAPITags.PublicResourcePolicy], request: { params: getResourcePoliciesParamsSchema }, diff --git a/server/routers/siteResource/addClientToSiteResource.ts b/server/routers/siteResource/addClientToSiteResource.ts index da585d2c6..c9ead2cbd 100644 --- a/server/routers/siteResource/addClientToSiteResource.ts +++ b/server/routers/siteResource/addClientToSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ path: "/site-resource/{siteResourceId}/clients/add", description: "Add a single client to a site resource. Clients with a userId cannot be added.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: addClientToSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/addRoleToSiteResource.ts b/server/routers/siteResource/addRoleToSiteResource.ts index 9f4922b63..2cdae780c 100644 --- a/server/routers/siteResource/addRoleToSiteResource.ts +++ b/server/routers/siteResource/addRoleToSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/roles/add", description: "Add a single role to a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: addRoleToSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/addUserToSiteResource.ts b/server/routers/siteResource/addUserToSiteResource.ts index 83476b189..526494265 100644 --- a/server/routers/siteResource/addUserToSiteResource.ts +++ b/server/routers/siteResource/addUserToSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/users/add", description: "Add a single user to a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: addUserToSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/listSiteResourceClients.ts b/server/routers/siteResource/listSiteResourceClients.ts index 90255e8cb..916345190 100644 --- a/server/routers/siteResource/listSiteResourceClients.ts +++ b/server/routers/siteResource/listSiteResourceClients.ts @@ -39,7 +39,7 @@ registry.registerPath({ method: "get", path: "/site-resource/{siteResourceId}/clients", description: "List all clients for a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: listSiteResourceClientsSchema }, diff --git a/server/routers/siteResource/listSiteResourceRoles.ts b/server/routers/siteResource/listSiteResourceRoles.ts index 51a34d7c0..132b4fed0 100644 --- a/server/routers/siteResource/listSiteResourceRoles.ts +++ b/server/routers/siteResource/listSiteResourceRoles.ts @@ -40,7 +40,7 @@ registry.registerPath({ method: "get", path: "/site-resource/{siteResourceId}/roles", description: "List all roles for a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: listSiteResourceRolesSchema }, diff --git a/server/routers/siteResource/listSiteResourceUsers.ts b/server/routers/siteResource/listSiteResourceUsers.ts index 00f86c588..70604d718 100644 --- a/server/routers/siteResource/listSiteResourceUsers.ts +++ b/server/routers/siteResource/listSiteResourceUsers.ts @@ -43,7 +43,7 @@ registry.registerPath({ method: "get", path: "/site-resource/{siteResourceId}/users", description: "List all users for a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: listSiteResourceUsersSchema }, diff --git a/server/routers/siteResource/removeClientFromSiteResource.ts b/server/routers/siteResource/removeClientFromSiteResource.ts index 26d735c6e..bf57668bf 100644 --- a/server/routers/siteResource/removeClientFromSiteResource.ts +++ b/server/routers/siteResource/removeClientFromSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ path: "/site-resource/{siteResourceId}/clients/remove", description: "Remove a single client from a site resource. Clients with a userId cannot be removed.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: removeClientFromSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/removeRoleFromSiteResource.ts b/server/routers/siteResource/removeRoleFromSiteResource.ts index 872452e48..cd60b288d 100644 --- a/server/routers/siteResource/removeRoleFromSiteResource.ts +++ b/server/routers/siteResource/removeRoleFromSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/roles/remove", description: "Remove a single role from a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: removeRoleFromSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/removeUserFromSiteResource.ts b/server/routers/siteResource/removeUserFromSiteResource.ts index 6ad275508..42b719e85 100644 --- a/server/routers/siteResource/removeUserFromSiteResource.ts +++ b/server/routers/siteResource/removeUserFromSiteResource.ts @@ -33,7 +33,7 @@ registry.registerPath({ method: "post", path: "/site-resource/{siteResourceId}/users/remove", description: "Remove a single user from a site resource.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: removeUserFromSiteResourceParamsSchema, body: { diff --git a/server/routers/siteResource/setSiteResourceClients.ts b/server/routers/siteResource/setSiteResourceClients.ts index 43f61666f..e1915badc 100644 --- a/server/routers/siteResource/setSiteResourceClients.ts +++ b/server/routers/siteResource/setSiteResourceClients.ts @@ -33,7 +33,7 @@ registry.registerPath({ path: "/site-resource/{siteResourceId}/clients", description: "Set clients for a site resource. This will replace all existing clients. Clients with a userId cannot be added.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Client], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: setSiteResourceClientsParamsSchema, body: { diff --git a/server/routers/siteResource/setSiteResourceRoles.ts b/server/routers/siteResource/setSiteResourceRoles.ts index cc733cc8e..7d1c8db15 100644 --- a/server/routers/siteResource/setSiteResourceRoles.ts +++ b/server/routers/siteResource/setSiteResourceRoles.ts @@ -34,7 +34,7 @@ registry.registerPath({ path: "/site-resource/{siteResourceId}/roles", description: "Set roles for a site resource. This will replace all existing roles.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.Role], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: setSiteResourceRolesParamsSchema, body: { diff --git a/server/routers/siteResource/setSiteResourceUsers.ts b/server/routers/siteResource/setSiteResourceUsers.ts index 0ad912831..06953b927 100644 --- a/server/routers/siteResource/setSiteResourceUsers.ts +++ b/server/routers/siteResource/setSiteResourceUsers.ts @@ -35,7 +35,7 @@ registry.registerPath({ path: "/site-resource/{siteResourceId}/users", description: "Set users for a site resource. This will replace all existing users.", - tags: [OpenAPITags.PrivateResource, OpenAPITags.User], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: setSiteResourceUsersParamsSchema, body: { From bf1cc705a5f71e21f0b2c83c940f3082ded60579 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 17:09:56 -0400 Subject: [PATCH 89/94] Update tags --- server/openApi.ts | 3 +++ server/private/routers/alertRule/createAlertRule.ts | 2 +- server/private/routers/alertRule/deleteAlertRule.ts | 2 +- server/private/routers/alertRule/getAlertRule.ts | 2 +- server/private/routers/alertRule/listAlertRules.ts | 2 +- server/private/routers/alertRule/updateAlertRule.ts | 2 +- .../createEventStreamingDestination.ts | 2 +- .../deleteEventStreamingDestination.ts | 2 +- .../listEventStreamingDestinations.ts | 2 +- .../updateEventStreamingDestination.ts | 2 +- server/private/routers/healthChecks/createHealthCheck.ts | 2 +- server/private/routers/healthChecks/deleteHealthCheck.ts | 2 +- server/private/routers/healthChecks/listHealthChecks.ts | 2 +- server/private/routers/healthChecks/updateHealthCheck.ts | 2 +- server/private/routers/policy/createResourcePolicy.ts | 2 +- server/private/routers/policy/listResourcePolicies.ts | 2 +- server/routers/policy/getResourcePolicy.ts | 2 +- server/routers/policy/updateResourcePolicy.ts | 4 ++-- server/routers/site/listSites.ts | 2 +- 19 files changed, 22 insertions(+), 19 deletions(-) diff --git a/server/openApi.ts b/server/openApi.ts index f80165d9e..27c26b2da 100644 --- a/server/openApi.ts +++ b/server/openApi.ts @@ -23,6 +23,9 @@ export enum OpenAPITags { Blueprint = "Blueprint", Ssh = "SSH", Logs = "Logs", + EventStreamingDestination = "Event Streaming Destination", + AlertRule = "Alert Rule", + HealthCheck = "Health Check", PublicResourcePolicyLegacy = "Public Resource Policy (Legacy)", PublicResourceLegacy = "Public Resource (Legacy)", PrivateResourceLegacy = "Private Resource (Legacy)" diff --git a/server/private/routers/alertRule/createAlertRule.ts b/server/private/routers/alertRule/createAlertRule.ts index 7a46c84ff..51cc8ea16 100644 --- a/server/private/routers/alertRule/createAlertRule.ts +++ b/server/private/routers/alertRule/createAlertRule.ts @@ -191,7 +191,7 @@ registry.registerPath({ method: "put", path: "/org/{orgId}/alert-rule", description: "Create an alert rule for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.AlertRule], request: { params: paramsSchema, body: { diff --git a/server/private/routers/alertRule/deleteAlertRule.ts b/server/private/routers/alertRule/deleteAlertRule.ts index 0439a6622..c3a21e8ac 100644 --- a/server/private/routers/alertRule/deleteAlertRule.ts +++ b/server/private/routers/alertRule/deleteAlertRule.ts @@ -34,7 +34,7 @@ registry.registerPath({ method: "delete", path: "/org/{orgId}/alert-rule/{alertRuleId}", description: "Delete an alert rule for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.AlertRule], request: { params: paramsSchema }, diff --git a/server/private/routers/alertRule/getAlertRule.ts b/server/private/routers/alertRule/getAlertRule.ts index fcbd44f11..92a53b93e 100644 --- a/server/private/routers/alertRule/getAlertRule.ts +++ b/server/private/routers/alertRule/getAlertRule.ts @@ -48,7 +48,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/alert-rule/{alertRuleId}", description: "Get a specific alert rule for an organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.AlertRule], request: { params: paramsSchema }, diff --git a/server/private/routers/alertRule/listAlertRules.ts b/server/private/routers/alertRule/listAlertRules.ts index 0fc720d66..c6835a45d 100644 --- a/server/private/routers/alertRule/listAlertRules.ts +++ b/server/private/routers/alertRule/listAlertRules.ts @@ -90,7 +90,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/alert-rules", description: "List all alert rules for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.AlertRule], request: { query: querySchema, params: paramsSchema diff --git a/server/private/routers/alertRule/updateAlertRule.ts b/server/private/routers/alertRule/updateAlertRule.ts index 690a97d29..87fa4675f 100644 --- a/server/private/routers/alertRule/updateAlertRule.ts +++ b/server/private/routers/alertRule/updateAlertRule.ts @@ -158,7 +158,7 @@ registry.registerPath({ method: "post", path: "/org/{orgId}/alert-rule/{alertRuleId}", description: "Update an alert rule for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.AlertRule], request: { params: paramsSchema, body: { diff --git a/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts b/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts index 5050c55e8..7b000c5d8 100644 --- a/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts +++ b/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts @@ -52,7 +52,7 @@ registry.registerPath({ method: "put", path: "/org/{orgId}/event-streaming-destination", description: "Create an event streaming destination for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.EventStreamingDestination], request: { params: paramsSchema, body: { diff --git a/server/private/routers/eventStreamingDestination/deleteEventStreamingDestination.ts b/server/private/routers/eventStreamingDestination/deleteEventStreamingDestination.ts index 5aa4bf314..c047b6c95 100644 --- a/server/private/routers/eventStreamingDestination/deleteEventStreamingDestination.ts +++ b/server/private/routers/eventStreamingDestination/deleteEventStreamingDestination.ts @@ -35,7 +35,7 @@ registry.registerPath({ path: "/org/{orgId}/event-streaming-destination/{destinationId}", description: "Delete an event streaming destination for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.EventStreamingDestination], request: { params: paramsSchema }, diff --git a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts index e20f95b8e..dc741d482 100644 --- a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts +++ b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts @@ -109,7 +109,7 @@ registry.registerPath({ path: "/org/{orgId}/event-streaming-destination", description: "List all event streaming destinations for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.EventStreamingDestination], request: { query: querySchema, params: paramsSchema diff --git a/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts b/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts index adbac2cca..84202bf8e 100644 --- a/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts +++ b/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts @@ -55,7 +55,7 @@ registry.registerPath({ method: "post", path: "/org/{orgId}/event-streaming-destination/{destinationId}", description: "Update an event streaming destination for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.EventStreamingDestination], request: { params: paramsSchema, body: { diff --git a/server/private/routers/healthChecks/createHealthCheck.ts b/server/private/routers/healthChecks/createHealthCheck.ts index 6f49f0f18..ef7331c5f 100644 --- a/server/private/routers/healthChecks/createHealthCheck.ts +++ b/server/private/routers/healthChecks/createHealthCheck.ts @@ -75,7 +75,7 @@ registry.registerPath({ method: "put", path: "/org/{orgId}/health-check", description: "Create a health check for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.HealthCheck], request: { params: paramsSchema, body: { diff --git a/server/private/routers/healthChecks/deleteHealthCheck.ts b/server/private/routers/healthChecks/deleteHealthCheck.ts index 90bb41e50..d51c42d81 100644 --- a/server/private/routers/healthChecks/deleteHealthCheck.ts +++ b/server/private/routers/healthChecks/deleteHealthCheck.ts @@ -37,7 +37,7 @@ registry.registerPath({ method: "delete", path: "/org/{orgId}/health-check/{healthCheckId}", description: "Delete a health check for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.HealthCheck], request: { params: paramsSchema }, diff --git a/server/private/routers/healthChecks/listHealthChecks.ts b/server/private/routers/healthChecks/listHealthChecks.ts index 0d18b881e..2e34b7f6e 100644 --- a/server/private/routers/healthChecks/listHealthChecks.ts +++ b/server/private/routers/healthChecks/listHealthChecks.ts @@ -63,7 +63,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/health-checks", description: "List health checks for an organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.HealthCheck], request: { params: paramsSchema, query: querySchema diff --git a/server/private/routers/healthChecks/updateHealthCheck.ts b/server/private/routers/healthChecks/updateHealthCheck.ts index 4fb7a624b..a4022d298 100644 --- a/server/private/routers/healthChecks/updateHealthCheck.ts +++ b/server/private/routers/healthChecks/updateHealthCheck.ts @@ -109,7 +109,7 @@ registry.registerPath({ method: "post", path: "/org/{orgId}/health-check/{healthCheckId}", description: "Update a health check for a specific organization.", - tags: [OpenAPITags.Org], + tags: [OpenAPITags.HealthCheck], request: { params: paramsSchema, body: { diff --git a/server/private/routers/policy/createResourcePolicy.ts b/server/private/routers/policy/createResourcePolicy.ts index 5b8016286..78eace74c 100644 --- a/server/private/routers/policy/createResourcePolicy.ts +++ b/server/private/routers/policy/createResourcePolicy.ts @@ -121,7 +121,7 @@ registry.registerPath({ method: "post", path: "/org/{orgId}/resource-policy", description: "Create a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: createResourcePolicyParamsSchema, body: { diff --git a/server/private/routers/policy/listResourcePolicies.ts b/server/private/routers/policy/listResourcePolicies.ts index 7bd17a5f5..af02b50c6 100644 --- a/server/private/routers/policy/listResourcePolicies.ts +++ b/server/private/routers/policy/listResourcePolicies.ts @@ -79,7 +79,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/resource-policies", description: "List resource policies for an organization.", - tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: z.object({ orgId: z.string() diff --git a/server/routers/policy/getResourcePolicy.ts b/server/routers/policy/getResourcePolicy.ts index 4ce032b0b..fafa31d7d 100644 --- a/server/routers/policy/getResourcePolicy.ts +++ b/server/routers/policy/getResourcePolicy.ts @@ -168,7 +168,7 @@ registry.registerPath({ path: "/org/{orgId}/resource-policy/{niceId}", description: "Get a resource policy by orgId and niceId. NiceId is a readable ID for the resource and unique on a per org basis.", - tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: z.object({ orgId: z.string(), diff --git a/server/routers/policy/updateResourcePolicy.ts b/server/routers/policy/updateResourcePolicy.ts index 7e4d6c250..e395987eb 100644 --- a/server/routers/policy/updateResourcePolicy.ts +++ b/server/routers/policy/updateResourcePolicy.ts @@ -22,7 +22,7 @@ registry.registerPath({ method: "put", path: "/resource-policy/{resourcePolicyId}", description: "Update a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: updateResourcePolicyParamsSchema, body: { @@ -40,7 +40,7 @@ registry.registerPath({ method: "put", path: "/public-resource-policy/{resourcePolicyId}", description: "Update a resource policy.", - tags: [OpenAPITags.Org, OpenAPITags.PublicResourcePolicy], + tags: [OpenAPITags.PublicResourcePolicy], request: { params: updateResourcePolicyParamsSchema, body: { diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 1f8e41f26..cdc3a5630 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -177,7 +177,7 @@ registry.registerPath({ method: "get", path: "/org/{orgId}/sites", description: "List all sites in an organization", - tags: [OpenAPITags.Org, OpenAPITags.Site], + tags: [OpenAPITags.Site], request: { params: listSitesParamsSchema, query: listSitesSchema From adeefb9dbd5e1ee5ed669b949fb7189ffaaa34e8 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 17:13:47 -0400 Subject: [PATCH 90/94] Add missing endpoints to api --- server/private/routers/integration.ts | 152 ++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/server/private/routers/integration.ts b/server/private/routers/integration.ts index b613b2f27..2e53b5b9d 100644 --- a/server/private/routers/integration.ts +++ b/server/private/routers/integration.ts @@ -17,6 +17,10 @@ import * as logs from "#private/routers/auditLogs"; import * as alertEvents from "#private/routers/alertEvents"; import * as certificates from "#private/routers/certificates"; import * as siteProvisioning from "#private/routers/siteProvisioning"; +import * as policy from "#private/routers/policy"; +import * as eventStreamingDestination from "#private/routers/eventStreamingDestination"; +import * as alertRule from "#private/routers/alertRule"; +import * as healthChecks from "#private/routers/healthChecks"; import { verifyApiKeyHasAction, @@ -26,6 +30,7 @@ import { verifyApiKeyRoleAccess, verifyApiKeyUserAccess, verifyApiKeySiteProvisioningKeyAccess, + verifyApiKeyResourcePolicyAccess, verifyLimits } from "@server/middlewares"; import * as user from "#private/routers/user"; @@ -259,3 +264,150 @@ authenticated.patch( logActionAudit(ActionsEnum.updateSiteProvisioningKey), siteProvisioning.updateSiteProvisioningKey ); + +authenticated.get( + ["/org/:orgId/resource-policies", "/org/:orgId/public-resource-policies"], + verifyValidLicense, + verifyValidSubscription(tierMatrix.resourcePolicies), + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.listResourcePolicies), + logActionAudit(ActionsEnum.listResourcePolicies), + policy.listResourcePolicies +); + +authenticated.post( + ["/org/:orgId/resource-policy", "/org/:orgId/public-resource-policy"], + verifyValidLicense, + verifyValidSubscription(tierMatrix.resourcePolicies), + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createResourcePolicy), + logActionAudit(ActionsEnum.createResourcePolicy), + policy.createResourcePolicy +); + +authenticated.delete( + [ + "/resource-policy/:resourcePolicyId", + "/public-resource-policy/:resourcePolicyId" + ], + verifyApiKeyResourcePolicyAccess, + verifyValidLicense, + verifyValidSubscription(tierMatrix.resourcePolicies), + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.deleteResourcePolicy), + logActionAudit(ActionsEnum.deleteResourcePolicy), + policy.deleteResourcePolicy +); + +authenticated.put( + "/org/:orgId/event-streaming-destination", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createEventStreamingDestination), + logActionAudit(ActionsEnum.createEventStreamingDestination), + eventStreamingDestination.createEventStreamingDestination +); + +authenticated.post( + "/org/:orgId/event-streaming-destination/:destinationId", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.updateEventStreamingDestination), + logActionAudit(ActionsEnum.updateEventStreamingDestination), + eventStreamingDestination.updateEventStreamingDestination +); + +authenticated.delete( + "/org/:orgId/event-streaming-destination/:destinationId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.deleteEventStreamingDestination), + logActionAudit(ActionsEnum.deleteEventStreamingDestination), + eventStreamingDestination.deleteEventStreamingDestination +); + +authenticated.get( + "/org/:orgId/event-streaming-destinations", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listEventStreamingDestinations), + eventStreamingDestination.listEventStreamingDestinations +); + +authenticated.put( + "/org/:orgId/alert-rule", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createAlertRule), + logActionAudit(ActionsEnum.createAlertRule), + alertRule.createAlertRule +); + +authenticated.post( + "/org/:orgId/alert-rule/:alertRuleId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.updateAlertRule), + logActionAudit(ActionsEnum.updateAlertRule), + alertRule.updateAlertRule +); + +authenticated.delete( + "/org/:orgId/alert-rule/:alertRuleId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.deleteAlertRule), + logActionAudit(ActionsEnum.deleteAlertRule), + alertRule.deleteAlertRule +); + +authenticated.get( + "/org/:orgId/alert-rules", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listAlertRules), + alertRule.listAlertRules +); + +authenticated.get( + "/org/:orgId/alert-rule/:alertRuleId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.getAlertRule), + alertRule.getAlertRule +); + +authenticated.get( + "/org/:orgId/health-checks", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.listHealthChecks), + healthChecks.listHealthChecks +); + +authenticated.put( + "/org/:orgId/health-check", + verifyApiKeyOrgAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.createHealthCheck), + logActionAudit(ActionsEnum.createHealthCheck), + healthChecks.createHealthCheck +); + +authenticated.post( + "/org/:orgId/health-check/:healthCheckId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.updateHealthCheck), + logActionAudit(ActionsEnum.updateHealthCheck), + healthChecks.updateHealthCheck +); + +authenticated.delete( + "/org/:orgId/health-check/:healthCheckId", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.deleteHealthCheck), + logActionAudit(ActionsEnum.deleteHealthCheck), + healthChecks.deleteHealthCheck +); + +authenticated.get( + "/org/:orgId/health-check/:healthCheckId/status-history", + verifyApiKeyOrgAccess, + verifyApiKeyHasAction(ActionsEnum.getTarget), + healthChecks.getHealthCheckStatusHistory +); From f1ed4da8a4094717fae9533e4a04c58f9800e79a Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 10 Jul 2026 17:45:15 -0400 Subject: [PATCH 91/94] Move to legacy --- server/routers/resource/addRoleToResource.ts | 2 +- server/routers/siteResource/batchAddClientToSiteResources.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routers/resource/addRoleToResource.ts b/server/routers/resource/addRoleToResource.ts index 1fac3b7db..9c5b6f0c6 100644 --- a/server/routers/resource/addRoleToResource.ts +++ b/server/routers/resource/addRoleToResource.ts @@ -30,7 +30,7 @@ registry.registerPath({ path: "/resource/{resourceId}/roles/add", description: "Add a single role to a resource. When the resource has an inline policy defined (no shared resource policy assigned), the role is added to the inline policy instead of directly to the resource.", - tags: [OpenAPITags.PublicResourceLegacy, OpenAPITags.Role], + tags: [OpenAPITags.PublicResourceLegacy], request: { params: addRoleToResourceParamsSchema, body: { diff --git a/server/routers/siteResource/batchAddClientToSiteResources.ts b/server/routers/siteResource/batchAddClientToSiteResources.ts index 558eaee32..6fde3b55a 100644 --- a/server/routers/siteResource/batchAddClientToSiteResources.ts +++ b/server/routers/siteResource/batchAddClientToSiteResources.ts @@ -38,7 +38,7 @@ registry.registerPath({ method: "post", path: "/client/{clientId}/site-resources", description: "Add a machine client to multiple site resources at once.", - tags: [OpenAPITags.Client], + tags: [OpenAPITags.PrivateResourceLegacy], request: { params: batchAddClientToSiteResourcesParamsSchema, body: { From 2bc6b28978d9cb76e8047cbdf0c70883337892bb Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 15:33:43 -0400 Subject: [PATCH 92/94] Unify put vs post --- server/routers/domain/updateDomain.ts | 27 ++++- server/routers/integration.ts | 101 ++++++++++++++++++ .../policy/setResourcePolicyAccessControl.ts | 40 +++++++ .../policy/setResourcePolicyHeaderAuth.ts | 44 +++++++- .../policy/setResourcePolicyPassword.ts | 44 +++++++- .../policy/setResourcePolicyPincode.ts | 44 +++++++- .../routers/policy/setResourcePolicyRules.ts | 44 +++++++- .../policy/setResourcePolicyWhitelist.ts | 44 +++++++- server/routers/policy/updateResourcePolicy.ts | 42 +++++++- 9 files changed, 417 insertions(+), 13 deletions(-) diff --git a/server/routers/domain/updateDomain.ts b/server/routers/domain/updateDomain.ts index 61b99f542..cc2a26844 100644 --- a/server/routers/domain/updateDomain.ts +++ b/server/routers/domain/updateDomain.ts @@ -33,7 +33,7 @@ const UpdateDomainResponseDataSchema = z.object({ registry.registerPath({ - method: "patch", + method: "post", path: "/org/{orgId}/domain/{domainId}", description: "Update a domain by domainId.", tags: [OpenAPITags.Domain], @@ -55,6 +55,31 @@ registry.registerPath({ } }); +registry.registerPath({ + method: "patch", + path: "/org/{orgId}/domain/{domainId}", + description: + "Update a domain by domainId. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.Domain], + request: { + params: z.object({ + domainId: z.string(), + orgId: z.string() + }) + }, + responses: { + 200: { + description: "Successful response", + content: { + "application/json": { + schema: createApiResponseSchema(UpdateDomainResponseDataSchema) + } + } + } + } +}); + export async function updateOrgDomain( req: Request, res: Response, diff --git a/server/routers/integration.ts b/server/routers/integration.ts index d16efdb63..13498ad25 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -426,6 +426,15 @@ authenticated.put( domain.createOrgDomain ); +authenticated.post( + "/org/:orgId/domain/:domainId", + verifyApiKeyOrgAccess, + verifyApiKeyDomainAccess, + verifyApiKeyHasAction(ActionsEnum.updateOrgDomain), + domain.updateOrgDomain +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.patch( "/org/:orgId/domain/:domainId", verifyApiKeyOrgAccess, @@ -531,6 +540,17 @@ authenticated.post( resource.updateResource ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId", + "/public-resource-policy/:resourcePolicyId" + ], + verifyApiKeyResourcePolicyAccess, + verifyApiKeyHasAction(ActionsEnum.updateResourcePolicy), + policy.updateResourcePolicy +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId", @@ -698,6 +718,22 @@ authenticated.post( resource.setResourceUsers ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/access-control", + "/public-resource-policy/:resourcePolicyId/access-control" + ], + verifyApiKeyResourcePolicyAccess, + verifyApiKeyRoleAccess, + verifyLimits, + verifyUserHasAction(ActionsEnum.setResourcePolicyUsers), + verifyUserHasAction(ActionsEnum.setResourcePolicyRoles), + logActionAudit(ActionsEnum.setResourcePolicyUsers), + logActionAudit(ActionsEnum.setResourcePolicyRoles), + policy.setResourcePolicyAccessControl +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/access-control", @@ -713,6 +749,19 @@ authenticated.put( policy.setResourcePolicyAccessControl ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/password", + "/public-resource-policy/:resourcePolicyId/password" + ], + verifyApiKeyResourcePolicyAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.setResourcePolicyPassword), + logActionAudit(ActionsEnum.setResourcePolicyPassword), + policy.setResourcePolicyPassword +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/password", @@ -725,6 +774,19 @@ authenticated.put( policy.setResourcePolicyPassword ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/pincode", + "/public-resource-policy/:resourcePolicyId/pincode" + ], + verifyApiKeyResourcePolicyAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.setResourcePolicyPincode), + logActionAudit(ActionsEnum.setResourcePolicyPincode), + policy.setResourcePolicyPincode +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/pincode", @@ -737,6 +799,19 @@ authenticated.put( policy.setResourcePolicyPincode ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/header-auth", + "/public-resource-policy/:resourcePolicyId/header-auth" + ], + verifyApiKeyResourcePolicyAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.setResourcePolicyHeaderAuth), + logActionAudit(ActionsEnum.setResourcePolicyHeaderAuth), + policy.setResourcePolicyHeaderAuth +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/header-auth", @@ -749,6 +824,19 @@ authenticated.put( policy.setResourcePolicyHeaderAuth ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/whitelist", + "/public-resource-policy/:resourcePolicyId/whitelist" + ], + verifyApiKeyResourcePolicyAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.setResourcePolicyWhitelist), + logActionAudit(ActionsEnum.setResourcePolicyWhitelist), + policy.setResourcePolicyWhitelist +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/whitelist", @@ -761,6 +849,19 @@ authenticated.put( policy.setResourcePolicyWhitelist ); +authenticated.post( + [ + "/resource-policy/:resourcePolicyId/rules", + "/public-resource-policy/:resourcePolicyId/rules" + ], + verifyApiKeyResourcePolicyAccess, + verifyLimits, + verifyApiKeyHasAction(ActionsEnum.setResourcePolicyRules), + logActionAudit(ActionsEnum.setResourcePolicyRules), + policy.setResourcePolicyRules +); + +// Deprecated: use POST instead. Kept for backward compatibility. authenticated.put( [ "/resource-policy/:resourcePolicyId/rules", diff --git a/server/routers/policy/setResourcePolicyAccessControl.ts b/server/routers/policy/setResourcePolicyAccessControl.ts index c74a01410..3af1c6eac 100644 --- a/server/routers/policy/setResourcePolicyAccessControl.ts +++ b/server/routers/policy/setResourcePolicyAccessControl.ts @@ -73,6 +73,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourceId}/access-control", + description: + "Set access control users for a resource policy, including SSO, users, roles, Identity provider. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyAccessControlParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyAcccessControlBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourceId}/access-control", + description: + "Set access control users for a resource policy, including SSO, users, roles, Identity provider. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy, OpenAPITags.User], + request: { + params: setResourcePolicyAccessControlParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyAcccessControlBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyAccessControl( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyHeaderAuth.ts b/server/routers/policy/setResourcePolicyHeaderAuth.ts index efbbedf8a..6c9c2dc49 100644 --- a/server/routers/policy/setResourcePolicyHeaderAuth.ts +++ b/server/routers/policy/setResourcePolicyHeaderAuth.ts @@ -25,7 +25,7 @@ const setResourcePolicyHeaderAuthBodySchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}/header-auth", description: "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.", @@ -44,7 +44,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}/header-auth", description: "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication.", @@ -62,6 +62,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}/header-auth", + description: + "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyHeaderAuthParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyHeaderAuthBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/header-auth", + description: + "Set or update the header authentication for a resource policy. If user and password is not provided, it will remove the header authentication. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: setResourcePolicyHeaderAuthParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyHeaderAuthBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyHeaderAuth( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyPassword.ts b/server/routers/policy/setResourcePolicyPassword.ts index 909df6da0..1255245e8 100644 --- a/server/routers/policy/setResourcePolicyPassword.ts +++ b/server/routers/policy/setResourcePolicyPassword.ts @@ -20,7 +20,7 @@ const setResourcePolicyPasswordBodySchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}/password", description: "Set the password for a resource policy. Setting the password to null will remove it.", @@ -39,7 +39,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}/password", description: "Set the password for a resource policy. Setting the password to null will remove it.", @@ -57,6 +57,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}/password", + description: + "Set the password for a resource policy. Setting the password to null will remove it. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyPasswordParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPasswordBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/password", + description: + "Set the password for a resource policy. Setting the password to null will remove it. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: setResourcePolicyPasswordParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPasswordBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyPassword( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyPincode.ts b/server/routers/policy/setResourcePolicyPincode.ts index 73fc26090..6537a3933 100644 --- a/server/routers/policy/setResourcePolicyPincode.ts +++ b/server/routers/policy/setResourcePolicyPincode.ts @@ -23,7 +23,7 @@ const setResourcePolicyPincodeBodySchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}/pincode", description: "Set the PIN code for a resource policy. Setting the PIN code to null will remove it.", @@ -42,7 +42,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}/pincode", description: "Set the PIN code for a resource policy. Setting the PIN code to null will remove it.", @@ -60,6 +60,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}/pincode", + description: + "Set the PIN code for a resource policy. Setting the PIN code to null will remove it. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyPincodeParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPincodeBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/pincode", + description: + "Set the PIN code for a resource policy. Setting the PIN code to null will remove it. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: setResourcePolicyPincodeParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyPincodeBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyPincode( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyRules.ts b/server/routers/policy/setResourcePolicyRules.ts index 9499afa6e..8cb5f4e09 100644 --- a/server/routers/policy/setResourcePolicyRules.ts +++ b/server/routers/policy/setResourcePolicyRules.ts @@ -43,7 +43,7 @@ const setResourcePolicyRulesParamsSchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}/rules", description: "Set all rules for a resource policy at once. This will replace all existing rules.", @@ -62,7 +62,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}/rules", description: "Set all rules for a resource policy at once. This will replace all existing rules.", @@ -80,6 +80,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}/rules", + description: + "Set all rules for a resource policy at once. This will replace all existing rules. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyRulesParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyRulesBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/rules", + description: + "Set all rules for a resource policy at once. This will replace all existing rules. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: setResourcePolicyRulesParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyRulesBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyRules( req: Request, res: Response, diff --git a/server/routers/policy/setResourcePolicyWhitelist.ts b/server/routers/policy/setResourcePolicyWhitelist.ts index 788419533..2cdd94e98 100644 --- a/server/routers/policy/setResourcePolicyWhitelist.ts +++ b/server/routers/policy/setResourcePolicyWhitelist.ts @@ -28,7 +28,7 @@ const setResourcePolicyWhitelistParamsSchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}/whitelist", description: "Set email whitelist for a resource policy. This will replace all existing emails.", @@ -47,7 +47,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}/whitelist", description: "Set email whitelist for a resource policy. This will replace all existing emails.", @@ -65,6 +65,46 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}/whitelist", + description: + "Set email whitelist for a resource policy. This will replace all existing emails. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicyLegacy], + request: { + params: setResourcePolicyWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyWhitelistBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}/whitelist", + description: + "Set email whitelist for a resource policy. This will replace all existing emails. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: setResourcePolicyWhitelistParamsSchema, + body: { + content: { + "application/json": { + schema: setResourcePolicyWhitelistBodySchema + } + } + } + }, + responses: {} +}); + export async function setResourcePolicyWhitelist( req: Request, res: Response, diff --git a/server/routers/policy/updateResourcePolicy.ts b/server/routers/policy/updateResourcePolicy.ts index e395987eb..f42f1bc74 100644 --- a/server/routers/policy/updateResourcePolicy.ts +++ b/server/routers/policy/updateResourcePolicy.ts @@ -19,7 +19,7 @@ const updateResourcePolicyBodySchema = z.strictObject({ }); registry.registerPath({ - method: "put", + method: "post", path: "/resource-policy/{resourcePolicyId}", description: "Update a resource policy.", tags: [OpenAPITags.PublicResourcePolicy], @@ -37,7 +37,7 @@ registry.registerPath({ }); registry.registerPath({ - method: "put", + method: "post", path: "/public-resource-policy/{resourcePolicyId}", description: "Update a resource policy.", tags: [OpenAPITags.PublicResourcePolicy], @@ -54,6 +54,44 @@ registry.registerPath({ responses: {} }); +registry.registerPath({ + method: "put", + path: "/resource-policy/{resourcePolicyId}", + description: "Update a resource policy. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: updateResourcePolicyParamsSchema, + body: { + content: { + "application/json": { + schema: updateResourcePolicyBodySchema + } + } + } + }, + responses: {} +}); + +registry.registerPath({ + method: "put", + path: "/public-resource-policy/{resourcePolicyId}", + description: "Update a resource policy. Deprecated: use POST instead.", + deprecated: true, + tags: [OpenAPITags.PublicResourcePolicy], + request: { + params: updateResourcePolicyParamsSchema, + body: { + content: { + "application/json": { + schema: updateResourcePolicyBodySchema + } + } + } + }, + responses: {} +}); + export async function updateResourcePolicy( req: Request, res: Response, From 26713b53f6006e551bfdc1a44816628dfd0e2a4b Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 20 Jul 2026 09:04:43 -0400 Subject: [PATCH 93/94] Fix #3462 --- server/routers/resource/createResourceRule.ts | 49 +++---------- server/routers/resource/updateResourceRule.ts | 70 ++++--------------- 2 files changed, 24 insertions(+), 95 deletions(-) diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index f75922722..4cb9967ed 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -9,16 +9,14 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { - isValidCIDR, - isValidIP, - isValidUrlGlobPattern + RESOURCE_RULE_MATCH_TYPES, + getResourceRuleValueValidationError } from "@server/lib/validators"; import { OpenAPITags, registry } from "@server/openApi"; -import { isValidRegionId } from "@server/db/regions"; const createResourceRuleSchema = z.strictObject({ action: z.enum(["ACCEPT", "DROP", "PASS"]), - match: z.enum(["CIDR", "IP", "PATH", "COUNTRY", "ASN", "REGION"]), + match: z.enum(RESOURCE_RULE_MATCH_TYPES), value: z.string().min(1), priority: z.int(), enabled: z.boolean().optional() @@ -151,39 +149,14 @@ export async function createResourceRule( ); } - if (match === "CIDR") { - if (!isValidCIDR(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid CIDR provided" - ) - ); - } - } else if (match === "IP") { - if (!isValidIP(value)) { - return next( - createHttpError(HttpCode.BAD_REQUEST, "Invalid IP provided") - ); - } - } else if (match === "PATH") { - if (!isValidUrlGlobPattern(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid URL glob pattern provided" - ) - ); - } - } else if (match === "REGION") { - if (!isValidRegionId(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid region ID provided" - ) - ); - } + const valueValidationError = getResourceRuleValueValidationError( + match, + value + ); + if (valueValidationError) { + return next( + createHttpError(HttpCode.BAD_REQUEST, valueValidationError) + ); } // Create the new resource rule diff --git a/server/routers/resource/updateResourceRule.ts b/server/routers/resource/updateResourceRule.ts index 0ca574017..5eba8aaa0 100644 --- a/server/routers/resource/updateResourceRule.ts +++ b/server/routers/resource/updateResourceRule.ts @@ -9,12 +9,11 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { - isValidCIDR, - isValidIP, - isValidUrlGlobPattern + RESOURCE_RULE_MATCH_TYPES, + getResourceRuleValueValidationError, + ResourceRuleMatchType } from "@server/lib/validators"; import { OpenAPITags, registry } from "@server/openApi"; -import { isValidRegionId } from "@server/db/regions"; // Define Zod schema for request parameters validation const updateResourceRuleParamsSchema = z.strictObject({ @@ -22,15 +21,7 @@ const updateResourceRuleParamsSchema = z.strictObject({ resourceId: z.coerce.number().int().positive() }); -const resourceRuleMatchSchema = z.enum([ - "CIDR", - "IP", - "PATH", - "COUNTRY", - "COUNTRY_IS_NOT", - "ASN", - "REGION" -]); +const resourceRuleMatchSchema = z.enum(RESOURCE_RULE_MATCH_TYPES); // Define Zod schema for request body validation const updateResourceRuleSchema = z @@ -173,14 +164,7 @@ export async function updateResourceRule( resource.resourcePolicyId === null && resource.defaultResourcePolicyId !== null; - let existingMatch: - | "CIDR" - | "IP" - | "PATH" - | "COUNTRY" - | "COUNTRY_IS_NOT" - | "ASN" - | "REGION"; + let existingMatch: ResourceRuleMatchType; if (isInlinePolicy) { const policyId = resource.defaultResourcePolicyId!; @@ -264,42 +248,14 @@ export async function updateResourceRule( const { value } = updateData; if (value !== undefined) { - if (match === "CIDR") { - if (!isValidCIDR(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid CIDR provided" - ) - ); - } - } else if (match === "IP") { - if (!isValidIP(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid IP provided" - ) - ); - } - } else if (match === "PATH") { - if (!isValidUrlGlobPattern(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid URL glob pattern provided" - ) - ); - } - } else if (match === "REGION") { - if (!isValidRegionId(value)) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Invalid region ID provided" - ) - ); - } + const valueValidationError = getResourceRuleValueValidationError( + match, + value + ); + if (valueValidationError) { + return next( + createHttpError(HttpCode.BAD_REQUEST, valueValidationError) + ); } } From df0198df9cfcf1a05146705166f8aa6ae75e5075 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 20 Jul 2026 11:37:18 -0400 Subject: [PATCH 94/94] Update internal -> private --- messages/en-US.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 2ca25d850..aff3be28b 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -934,7 +934,7 @@ "newtVersion": "Version", "architecture": "Architecture", "sites": "Sites", - "siteWgAnyClients": "Use any WireGuard client to connect. You will have to address internal resources using the peer IP.", + "siteWgAnyClients": "Use any WireGuard client to connect. You will have to address private resources using the peer IP.", "siteWgCompatibleAllClients": "Compatible with all WireGuard clients", "siteWgManualConfigurationRequired": "Manual configuration required", "userErrorNotAdminOrOwner": "User is not an admin or owner", @@ -2361,7 +2361,7 @@ "resourcesTableProxyResources": "Public", "resourcesTableClientResources": "Private", "resourcesTableNoProxyResourcesFound": "No proxy resources found.", - "resourcesTableNoInternalResourcesFound": "No internal resources found.", + "resourcesTableNoInternalResourcesFound": "No private resources found.", "resourcesTableDestination": "Destination", "resourcesTableAlias": "Alias", "resourcesTableAliasAddress": "Alias Address", @@ -2384,9 +2384,9 @@ "editInternalResourceDialogCancel": "Cancel", "editInternalResourceDialogSaveResource": "Save Resource", "editInternalResourceDialogSuccess": "Success", - "editInternalResourceDialogInternalResourceUpdatedSuccessfully": "Internal resource updated successfully", + "editInternalResourceDialogInternalResourceUpdatedSuccessfully": "Private resource updated successfully", "editInternalResourceDialogError": "Error", - "editInternalResourceDialogFailedToUpdateInternalResource": "Failed to update internal resource", + "editInternalResourceDialogFailedToUpdateInternalResource": "Failed to update private resource", "editInternalResourceDialogNameRequired": "Name is required", "editInternalResourceDialogNameMaxLength": "Name must be less than 255 characters", "editInternalResourceDialogProxyPortMin": "Proxy port must be at least 1", @@ -2412,7 +2412,7 @@ "editInternalResourceDialogAlias": "Alias", "editInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.", "createInternalResourceDialogNoSitesAvailable": "No Sites Available", - "createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one Newt site with a subnet configured to create internal resources.", + "createInternalResourceDialogNoSitesAvailableDescription": "You need to have at least one Newt site with a subnet configured to create private resources.", "createInternalResourceDialogClose": "Close", "createInternalResourceDialogCreateClientResource": "Create Private Resource", "createInternalResourceDialogCreateClientResourceDescription": "Create a new resource that will only be accessible to clients connected to the organization", @@ -2441,9 +2441,9 @@ "createInternalResourceDialogCancel": "Cancel", "createInternalResourceDialogCreateResource": "Create Resource", "createInternalResourceDialogSuccess": "Success", - "createInternalResourceDialogInternalResourceCreatedSuccessfully": "Internal resource created successfully", + "createInternalResourceDialogInternalResourceCreatedSuccessfully": "Private resource created successfully", "createInternalResourceDialogError": "Error", - "createInternalResourceDialogFailedToCreateInternalResource": "Failed to create internal resource", + "createInternalResourceDialogFailedToCreateInternalResource": "Failed to create private resource", "createInternalResourceDialogNameRequired": "Name is required", "createInternalResourceDialogNameMaxLength": "Name must be less than 255 characters", "createInternalResourceDialogPleaseSelectSite": "Please select a site",

rE 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 xS$Sp|pefQ>8NF&YPSE-fn1VDe&_GV{r7u zWGRD}Y6(y5Du6UTN|bV|f9v?7WbDR}hQc5_^XuC9lY7aMYNIu!daO>)#+IjKzEroE zy$ZeHEmNW|jn%=$^+Hu{E7cz7@=TX%>c}PiJ;r)a4>GTn1Llqd_Vc>qpu!yYKJyx2 zh5{(BOoj!%ySGCL0V#f}OEyTAMCBW@o_hA<_DiufOW8;L*(3tI{*o3enF^X!xh4_m~Y|c6h2&V8w^-3VG~A{Mo0OW$23b|2_4$!AO)zU*g#SHAwS zpzM{_4{4*7a(P7b6B2!#KymfFe!l`p0`hI>I(*a|B=WZ4K9``3;6!bSkjoaPCcrVj zjtGA51dLQtQj);UBC%mAh95ef>!6njfdb*1URjWr7f*mNpqRwxSxvqS_brl?EAZoB zfv$N0+~-2b0(_d0A$h=OWBUFAZItn-blGI{_3mKN9#xx0KC)wX*FCb~o@Y{N2kd27{;*eD?8Z&B8tYUYXi z4j)h7&n)SjtAGKJ#=N4fnR}l3zeW~m}!OHzAe>l;3t(*Qcfm}Mp8783}%UAlkg3Uh^w*;IjmdrD=Po7C+PwcIV4##0+UR72IO8y+HIzrnbs^gIwcND+efTh9Ieg zmo~`80X-O|Bg|Vz#DLPK|&KUUX(mvz%)%CxW1;hLNdMKpAhGej00j zy7D}tZ*{Fy4WxN~JeN>d4SX~k@;>WIui8*6p&=WVw2jwWSp?fPN)TJZs;@W8YAoaY zvU|Oe1o+|JR*Q4AXaHEiS$m6vZbdG}T4qD8uonuDS#M}Uq?YUMLF=2{<~^u&v>rGK zjnp`0#7+I$n^A*dlN)`wCwROUD>{07+pkBoNi`p!T0M#XQih)^)qxM($doEKBKzwIv^<-P9<%G z^5vQIK(arpALx?$%$mB06Oj{O=;{+)6Mf8?s@>H1RO^7^OsA5ZFGP`*S81D6)1==8 z3yb~=QK(M4_1v=E_PvuGx@*`R8R6(kPU}snYZ<376ykgb;q+gR6TNeE1_XqejxI6) z30a6mtsrEs&gHi&pQ31TqKSlYiJ@g*dWT(R2@duQU3z!auuwkK(rEZ|>UzOLnE7#d z+K(}zhZ@OQ4#Ni6VcNn42wk#B1j-D32zQZK*@0h2_D^J^o>?V{XnmqDg+BGJcty8n`jzw1TFE=8 z3ENLL1_~lkIGCw0kWv%wf|2Eyo&2bZBy0u0(hd`SDL&}%^x}+w=gNWkl$k<`ZDz?r z7H2svEv+LjV!i@T%8N)%5O@s6FJ-dm*>6UWQL89)m&TQ6{dA2`+6V<_i7vI36B;6sWna7StX7YQi#dB(ECHH3Z^7uSKHyazAY`AXs?OZa*6QgcRo z^=`GIWK1ps)hx!rgURe6`s0NhrIK{$d3Ko%*H+3VeKZ7)Lt~TMOvlssc;m1q%UJlm z0aTE<_wmK9$A(Zz1FUKQ39IO^no_4s*;Ee3Iky!`q-wtYHahV&LY5i!eRcHs=h3RB zRS3rnHpGg#OhsyTlq?sU5&dN@NK29X<-XSl-iP4LC&7qYx2_ksja!MnEG#Stocd>a z$LORB`N~dJBU0S7ABJvaFOWvfmD(0whQ5;*qP0_HA3sjGb)ju(qGU(#a5Ln)2k47s zVP=at&@m`}UFDQb1?{emh<=%nkZ z`~zNYmoC-LkYW%&lxum2{Ty5|tG(w_)9!g|Cq1DO{5U1qYu$PJQJ#X5?$$ zb5GeQ7iR`L6fF+14R6#TQV5$DCevbD*3T97!QB{wka&oLzL>R%ZrJc0^+b8uRj#+{g6f* zlj7n69Q;tWqi$pDNh{~}J!CJ0a|43M)5pff>>*lPfd2N`s@&)?N);QK5@%wH2pM}Y z5y!~bf)clD;o#FW38vBj$54o8&==>bf)Y=310CZ_=tFi8w;I8SgUY-bhGyE(I|Q9u zuEW75B4Td>#16X>`{Z4F`ecVyN%lFR4YT&oNQ9qXKtMn=c~83R%EU>ORw$LsfeEgG zwINH9BhG$+4>&foWFj^XcGKQQat-Hl8DFVCH0M>UN zVL|oPv$SBp_9hy7vxPydUhhEwbDe^(d>RR^^VuZ9WmM`a4%UNv@jc~a`jMR)0~yU_ zU{376jdS#zH*xyilC?faxf#$1%nSi;u-jl_Vd1x6#l$h#ommxUP<|X)F;+tRR&|uP z2p*Ll+~WUB8s;|S^MzgBdLP;9`w&knDwYI8cfTM0Bj*t&N`OkJv;<20sA|4vJPr!F z!C0HNr)1(1kiRVXM7mdM{gso_(>>+*8jeHQ^HU;7PHVoW?0R2N6l%8kbdN=QGC$hu zK#J~uMIGlsx5vW&IUpqYy?n1?*c!tAIQH<;r2~BPNegG8Ss*^#+%e?q>#LD`$p#Wl z6!>ITDC(MdOHb`XZsxW%jERDJLS$I+3xLr8 zetCFLZM}f0Nv|8A;27IG!67pS^|E~{@z_AQ9sUZuNl`Bx)GrurZ;xz&_lLjmcZN0& z9;89(i~X7`>;5NP_9XW|uZ-1n6wpajHX z2^#?vq9{1g3B9nIu;;w)EdJ-b*_HpCe##E=r}+UGh+kGG+8sJLwsMW=JojUt<$g=T zUitOadbfvTn<;kn6Sv>*%>RYR3l~o!e|RZ~OtzgdGdtlvr+rTHIfndM`1Tgk6y&40 zer=s3&;F5q^&Q04QVlSa|37{&84~c@-1|F}RaS09b=A#J+zA0#Rd(;hFER=D{IKAW z90)tMU5>oZv1m>oI62PINqE9H&&w}*zM1p&=Ta@L6T~W-zcd&r^blMBFpN`oqd7R? zpI*fTRTAdrtGQGEc_sK9!(sAmMqc=vx_cj={lnwR-5Fev1sWx!!xIhRY362BcPi}K z6$u{M94O&g#;IOAA*VnA-0!s`f>lzqs86SDsca({r zzhL%Y)7)9hh6`l1kIy@Xdk>yK)5iK4j(xq~Ip?%Ea?Hd;Sy=che0y4g?$NkdV)k+3 z{7${noTUl-kDokYjXY%qk*OrPYi8w#7>ytivrwJYis~sHni9g%|}Fp_Mc@-`mE| zj@GzVd!{me5tC^1nhMgztTSXFNa58`Liv%T)YK)k1yk5EPspx!E_%WRllRNb@uxdO zAZ+e>LpmJeze=)yxZGX(pQ&*i{+Sx*|0farKOO&d{#*Y)KJ@+%jQA7C5NCoH4QM7J zsD$*ZZT$S6_wF?x#P&aR>X|$qN?s4fKKKoMD=Skjn3(uLkiu2+1;yB~iCvpUkrUnfi>}oVwXaNcGprWwS(Vq$d=}`>A?hO_9>Z1Q2^9!PoId6lJ*uQ5}T? zyw4}S*+0)-oU+wXAv=7Q7|YaTwu7MK0K>>tSwfeBUJPbMB3Vnb>JX1V)8Lq#quC76 z+8NH%YHEZ4lOiMWMY1CUj@5Y7q$GSH&&n_6)p^io`y^7v+Rf@{;eA>5FD!rp(U;1@ ze?2+&X0-v_kkH_QMhi1R2nT#7*E<$lC=u>4g~)f^lk;ub7e+RkJ`KN-R@lDT-&T)x zE*}m4@#MSydU82Dxu`Y<(3@R`2M}nX0C?E<3q2ai(U0=(QXZy=@M&gY%BcwA6H z?ydN7{Wf7o1;(@4pgwy) z!2PuW=$N{ZWa=!x>W!lo2Q6l=FuhU#b5im5Z;sKgF4-S>_}Ya)w)jpKHlPvt%}(Qi z4pNemaN(WDp>;MWOK?rvqQWwS(cOSQvBiM5BRl~TC5B4*~Q5GhCqX;Mh#j@ADb z6BXqJS_dT)lc@QG>b7t@m=_fl0T@$M#EtThR1+Q(V-_@R(l|xT9!a-r`OlGxx%1-_ z(#S4oGdx7{DF7~+-n+oUh?r`GIIfm|tIXq%!^{vg3n7M>n=g%x?AQ@?xgy=OPkSz6 zcF>MhFAbYr(uu7{DXW+7ldvOco50{`U4$ zNlDLAK2g8Sj(Vp=HZhOx?^0s^8bCRy`3c(*on6B0e3b=XK1oGJ!y34>hbONusZ(-r zm{hZ4`!Oone?3^rgW26cBGEvvaHnEoumxty%i(l^f%#}*q&`3UKHRK8M%1g>E&uV` zAf?#_@Z`1GvcJ|+Ae5>1;3>iR|8#h=InQe?u@;{MiuOg`4^$@@Oi~|PRn2z1TF7yB zrf!&-^v#a(kE2uns8n|i_2^c1m`-SsY+ntJ1dk`5vqb^5eOXQ}q=*qtf- zG0kgIVv>K(3@G}3%K(+Zk&YJpf}io35U%q51pA)QqaNoR%l`APe{9B1)_--O=SLUr zplerAky8X4p>ndG#h#eUl=^&`fT5uTAZ0fsb-bFr&F9ov`AAw?SNZkF>gRNWB4E+< z2e5IGk-g4+%d+rOl1q4ee7tfTi19^Nd%mrDT=8Sloc?v=uAev7m&}t_R77Ad7kQA9 zeng%AH16BEj%dp>^rc04fa$23pfnw2Z^XX`fpm<>`ZG5B0ukcLtmKNn9^ zU0O$irGU}vACk0F?yuXv9{;h~J_sOH&U=$Ynf?G_ZgdhEBO!jbz&MG-r%l2gFMd2M z<*$c5^K+{1xM7^u3mPbP80vo`rnpm%bzD!h@8ciBKh`+?uXVM^2>xaZ90pZn@s{EwyZ<_)y1h33_--RKMZQAutg@xxAItgaA0vA_N9ul;x} z=I_6YO&B6GjwKDsan9b4LEP!4)sK<RoFHMH{(8}p_e$! z;I*HXP(VE7xrcbo?lF1Szt-&ApKIsLH^S~!)6;95to|_*>({f6UsyC${iF5Ce~o6s zUp)l(?Lwpi9GUg2ERrW`owr==`O-PNoLb307YFOkpF4ArFJbut1$vw4+HMm1^}lgF z%HCk4=(o6MpU1d`y;z?8$G+oa{541NV?UPCdU05$?x?n7Cu@#vOB}84(lu!;iI#pm zO+Sw9A=RG(V|l<&VY2`M%r!&HIlr%)?bhc-2}f(ZeQwcnGo2VM+&=j zef7b8zb}zF)y27Al579*HFsB(DPG)h;S#=WQ}EGMWG4fMO%hnchqbl{e#x`?@qs&ckmxsH2-yl z`+__F=(qXT1*FBC^Em(g*FU^lAwOUKA3px~N1NP#bN}~W|L{(nw1h-#kN^6*Oj=DM zPLTil`bPSnftVef{~3s%Yvg}-AO?OP8W?yoD*)Zc=Q3NOwE+*y@Fk>kJV+Z}x?+Xv zwmMmq1~qTWp}ncANxB%ml!ZSyx@WAnZt0TkdlyRzPAxA%PFyI8`ig-N>G}2RzlBIU zoG`0PK}K}P#aSYpJFV0peF>}f!{fbr6j$Ajk`RAu-bKicJtAU0lY}~Q=i)VzY^ZA4 z69!6Kw`>s~ab{ba$SFFX^x+5?LN=%bi@$vZ(CAjj>0Z0CBo#L|H;~C`K0rX{C=m0h zPEMOQZ@$@f_wHRaipBa~DOp)PfL$UI=aJqRe(bc1eE9I;EjpvyVW{I2ZxVMO-+|IwMxId%%;<)EkY!$ZhHNVq47@SUD7J~7x%<0^iZVndzdL8 z^p27+Qx;h8y3uEDa=5=u|6*}@(I{08wKyloTTZI0tAiz`3n~3k$gcV#PfRT5MdZc2 zIu&^$!(trTl+$QM;v9_r8RN`5yAbpLgT^>*5w`;cx{7X z(z+R0uekQ#-iFaY0np>qW2M{q`F-=}-RZm1_UZfts_RsV71!xx+WI&F zk5I*6vWm{^MC3SX=*08GR8U%d{DcJCn3>68?HH-c-{f6rqbDRWtwNwaB%0NHuRrHgAy3Ou4x3WTbAU zFBdm=`V(Sce!d>9BP8k3y!Y=F0A4&Ed1C9@)vKSrc_Y7e+cA0MNuniM;G#dbY!?Q)^XPtvijjk(fYGk0M~D} zAvHF_a-cd&J2iISzT14ecORtFZ?tuEM1aytA}>t^EHA>2BQKx1WoBkZ9e%Lmv6w}y zq^+~(frakEL!+bN#7?7=y}Z1TdC7ihzC7vT9=pCxiMD=8ihZWhh5R z?2^~PikfNE4suM~4d=y->o}NpDXHOvUtH|oDyFb&fx13e$w*0wxXaJiou%p^CEbK7hIbE;?(hI864jLu zqn-L$p19x}XMr!Ig*`L3;;~zhdkN&(= zaIvVh0jG5GNRiFPSzTyTYjV}pCxiGce98w8es-U?;Nm9Ez;*6QPfOEyz{|sPi8eL5QOY(rXR@6Y5=$edrM>+HzpmbMrmNG! zX0@jFC& zMVw}Yd_urK5_KJ49S_gY(M`O7TzXWEdSZ!3B5|%o4bjT$OPI@a7_b%(xyfMxehOS! zWmTH24l%iwjV-1ov7_^A?nl&t_P6-9v*hC>N!>_3-s=-CwsCPgvg^>u*y1 zYoysXF!LTQ1K7vq71d>`i}#96a%iSQPqn3_%arwPoAKT{B?a#C-J-01f^jNmx98y_liC z3{+PqU1atleYw1oLl*qiZ>Z<&*G&OYv&{`hE5MEBRo@Z8+r zXoCgE_a8VQd*81Dl6uYh_0J%f+DbyS1Nn8oc9pwz!wKehiqcvfqz?SU$-7+i+kTW7 z2w^@UAt8lD0c*FPP`rBe>W*Vi+zkv2tTAac#8Jz!f683+H51N(zxd(dfIrFsqDppS zxY9@LHQF4lQrckE6zn|EwHT6b*Li$ZJOIOBmu3Wit zzoKF%IN78l=;YMYRIqME?YptG>)TreWQ(ujSWG4OIko}h4u4@`42HXDIc^AyPJ(C~)>UdmCj>~k>cY~yL4QmE^A?-4aI zIT@*we&y3+kto~V+UKC?kjUEApJe!CW@g5^w^jreVi)`)i1j;dnT|xF*bepB;ML5` zNp6g*r~3Vzrk_F%IyQUqu3?Hl&BCu!lvL0TB?af}j@`TEak`v1*DA;+>s-CLO53qX zX@djdm;In4)J)sTJuxw{ZQC|N+^^+u(~da0f4h*-(ACmC+-_oaFUIAS{3tt4y}X0N z^tnH;>I?BkUL-XvU9*jI_UxRn_xe5s+MU-sGVKTdeBaMvXD9Yxr{>|Xu&pD2R$b~S z(d}B-VRdfP@r*sP(HK6Ictr&wrmlOWzP^WvZuChd6t-7R6;A{+2*_a9tXlQNv?ie& zd~DqV-`{twjuJ9Z4X$`3J#A`&!l!N^OZGQdX^ynVC7+xKvN&ks=OMH1QlT%aOxt=W_KOrwO^2Rc`z%9^JF3_ep zVirk&1js)FIi1AH`)Y32AJULH{t|U4r`iHlZn{*(8!DOAB#2bd8SO#m)!HDscIak! z>s~W4OQZcDy699=QCbDh`G zVDT_yPHN&Z*PhUms8Di}EbDR~h3nq$jmKL142A-cv+opR^s;Nu{y6Rb7kl5qRpqrs zi<(GGRAQGR#3&*~k*-ur6zNC@DI&ciyLvPJRH z79nIxkqm84;0Qo-x=B~=cooK80D=A{vKDa6rB zo2dHmVItfj$Yf<^keZ&VtIRbVS)^a!QyLyMc=bGsj*f;kT`JtXm?p{SNMcc9>&WU) zlnhP7yP#QG?_KVN7n`^rx6a639=Ek|T{3!0M1K5?q-4=-veA zo-sQlB6sJ`{Z>wFd+am#9IQ*AlLnLv3*UeOl+Btz9=oT9%oyoT zT8eU}zO6=Iv8@DDcB#d-WPR(qrAU{E0D=?zVsIq{S-b`a9R#m-V6tC}ebqHIFpv8z zk42w43PL{hs=KcYA@8Q)H7%5Syw=RxFp$_O9|f|B7Cg3`A<#d>$EbSFx>E6tZ#B+blOXHw2q&=d`w@!j`#AJ7mwP z&b@s1?&`kfp~1meY-utAXp($l@bLIJz!I^jvfC+ zf4reC4MXJHu!zdg4a)w$^ZRBOb4;qbg;7g>K1~ahL8+;&tGd;Gm6pADD2A|@!YKB# zanFV3$lqf@Z?rrKd6#E7xbymrEg6~q z5*WM*1q&-0j6t#=Ri`8K-ZL~5AWZ?CL;QN}r1m@G(DfZQ$h z%D^#p%>YkNPemaa85y*jm?Y<4!#~(d1{c{SWNp_FQ4}ELY!^N?d5}?DC4F>q@|+mP zx3)$JIWORxQfmey^8UDI&#m9yE1}d}eB~&eXV!f~)ORZuW;iufRaG1LevK%y?JwJAC-KVO~GoC98Oe zo)1AH$(4JozRa{0+nVsWZFs_dhx6&7!s23<)2Dxv2y#irZ%e)Lbb{Hu{rSB>1Pc;+ z&RSxxQR?CO&(Nu>s|O%}5@-UseLLy8SJ}<>^+pm&p7_yA*7~e$Y%SbpU{?GBX%+^T z?%=ojlBJe!YD8K((J6!Q9c*Lp0(pKq!pU>R*V9vFG(5)fz*3kmuJrpBn&6H`e{zyu z?NN_SGAa%Zv$i@q zwxWvwk!v;lBgv$$w^t+2q+Pl=AJ;qqt6rSgtnA!N&G{QvNdZ2iQaa!H1+Xfg&ogcn zr`-Rw{n3F_5pzNn)ctw%9}0KJSuZb^9^zY_tF0s!^T(rXhQ;in3%;fJd;oOPYJNI= zHb&5KPDLZ<#yvD0sBgMaKeHT2zjyaiZI@h+Ld(K8D;G zX_kZU{H9Jn>B$;2Xid7+JEJy>ToT5_HUn?m6f@K>wK_DXD6$?wUI(KTzNbGG7Zp`k zRf!VG`|rEf&1g&*#Sr9)$h&}^yRb9cCC>Gr1(gpRxV?y`kFyqiubIvHgg&P=)b#dB z;WFe>=e6h)6vO}RH*N7FY z==}4kLaLG!szUjzf4DLPp1Ykd3G?JUBMr<3M_Sfq^NX@L4R=x|yor+UR{m($%8Cz- zxN#P_ICUQrR$*--l@Z}RMPt`25HZ{b?BSHG-7juF|F!FtP6>Q@xp~J6hE}|lu4hi4 zHUwVbQJbPHM~@yR%xqp8^qiKbdmp{v^?AE7UgOo>O3Cer6vfma zI8^v8+`FHS5S50#sU-FdyndtFA9&&jDSK4N7JGl9Bh-h6nVQJy)9({_t!)I8kcpw_dnwfpgzIIlPSsMpudBQ~P@Hob zYk5+$54@XST(_Qt_bYBdyVm(d7{zG010pegIqoFYSXcv_$2bJ_@H0JJ^DtF50%kJa z?1P<4di$U}{6kiqZex@N1k*QpF5SMdApex6brFb%nXxiTpUs(yWuiR6f-JNuOPHpN zPEBRNH@Wk~A*%$PaE>8)sH&S2Mgsxt5R)%pN0Lx$EmAtnEEw zLKtv*Ldim;CfuRKmc(PuVYM5?5k<^|&%TI#&GKFYr*^9^E3DWNQeoU;=#I)W+Vq}? z4*2Z`eGZE?AOaMUep?9eCW~YPp)bbE>uTV-faS2rtG%>vG!RGj;1U(Xh7%v3#7FDjJt&1#s3U%Piq@kzxU_XC8q5svO{S2t^(0CR_Q2}3HCXF>eyWeJ zukTSdl{@^0EQ|@{OUrGwa_X)1xEV^66cGJk)v}>|U6PWShNVvt!PZ)wt!MU+n*+d|?$kP&v+#c*+#O!*7 zMCV4!Ap3JDVy*7o12{Zss>XL}orw`}qp6A}K7sAnrz?lfxWwZ`s4n_f-W9Wz`-5_^ zf~r69lKy7^TdFzwq37*w=$Zh%4t?khcB!wZsL0(;l#YDi$5_$alaC|7CA2-&Tas+h zNKeJ>QPp?){?dEZ$W96NQe71|3i`yC_al=8Z0|uv`Y`hQQZ%E)Q3AR?>Q!m!2yF1k z&p*FmKS4Z>|9tg4`0hRhJuRH2^4A+{*=ebsq^RYk?A*0WzNa5Bs_oE+eU(*J+v}ff zZSzd-3}7+k)aikS;Rhx#7@%5TKhJ(BJpW){H-&x?e6SHtu^}#5+yu?G@_u{xUl| zTkJ6V0-#^u-BKh8Oyu?V?|;LQkmBIrU~Y3>?mgf!OqI$oHSNr`oM}Jwp{$#-NYr`(LqFi;0p)o#nC<)??$UHWk6&AimP2+=U!+3|U%e;mxhibM z_l7)L^Mcz>AEhD-`^6K(Nm>mQKwKyaj5bC`Ysq0{o()d3`z)-Hcgb%EbzmM6xb(K2%j@x!7lWLWQsSk)Q%*~1OUi#R(emZYf z`vkwu1@HbaM*p4?`%Ew|H-7*9%0zF8uiMtDLB;4}unuRWBkm|pjgKb}R>=Fw!8$xzJKOb@YW$wr=%{kPtGB zkdwEOfctFa=tlFunCmw}J%&3ee7z7E6O;QsJPij(nXIcfRON11y!A?_swLNmOQYOP zC^-#J4X+WP!9~h9B7LwKm;{Qs00({+)V|4R4l^Y#bZ^J;LTwibir z3@9nN-CRsn!tms0{)qXO?d@qs1^0pqkNW!wHya=BSl}EL1C;nYVGyAfDY#Eg%`6 z?EBMmN;DClqTI`_g)NiWIDV?;;5c_1rGhe`nUuk7RPv=uYW=#?N>Nc!YGp1(sLlA& z=HF6Ir@F3>e)ZYqO~WIeqhBx0!U}{MHeQkAqJ_kjft9|bC^**FaZ^DdfB>yfNcWO> zN2)PJNcaU32_^}*i>S?EUfn*jnIPJe;I^4aO zuvwG{Fkejs{e}~`X0uho;y$xfKKY*dgFI5ysD)RX;A?XMA-KY~5OCD(>^+y`1vvPf zhhEw9VDi+0+Y=ujCA?ePjg{H>>`$L$z)M!Bs-c6N47#WdtFe%loJMbbp833Bi0 z_;q%{*RL`6`?>Ml9|*IlMkIZH_br!Zp)K5jv{Ak|Tf`*{D{Dc{Dmx}>)BA%3euu^r*RC&uMfg`&YaA)#j;!AYOFrZp$+y0^E`huI<45~+Z+dse zUNP@u)Q*z3ZZX;7(vJ@b&ROscMMi9b`-r#lmrSkl45Z#nB}@z+Sa;f>#)#EWAZC#Z z)40E}mYrc7)*fnkrbc;gL=I^=SiYw*;-pWZZuP;^)scbt&BMr$L^fthwcSgKijslB zs})T37dj!e89W{CNyTuBlu+-tRchSIg0x&5Nht|jTUr}Cz);1`0A_hfrqi87Y$|Z$ z?bIzK{(sC@xa%MYo>3w8>YtKKYm$?pcMc;5jT9)q>p-Q-7Non0;Pk^qmz4_@i|Y&H z>3|eojvt@Wj01hqnwn|H8{wSQ4P};Vrud3PnHCB;ch83!sNLRo8UlulR>`vEbtG#o-~p@s zI!*{VDS&9@OqU0{nK=W1)7V!%uwhT z0XDX}3#tz)@4i}pFtCuXU9lV6{_OaWnpm;xdVZ8+LZ0F~QVCKe&PnSU!#74QF0TjK zCINC*AH3Gb>h7lHhDc69AzjzqeH)_Tf^`{G{hZh9-rYh_aGk5RNiFQ|U4DfyecQFr ziQIM$fSzDy)z~r^N{_jbdXxDrAo19qUb@xWr^j(di7gNH>t3>AibAz<_Cs$UN+6uq zvHZ&5;983pa{#qp5t}@A_;7qo%r6yA-31oPsG$8BxBcUn`?oKY?N z3zsVQvPMjdVM^E?2R)IN&-{2B?&vS4Y)wzyc&)RhZrYWrnjKHIJk_3N(fn)aXMfaK zH)pBc-RYJ9S`s(754}(g`wgtBW!UmD(GX$RFh#l5l6xVE(dMQ{EFL2uAzjXe5TRl+ zkXio2cpn$%ng(lJm6j_e)w5^{{)U{k9Xq}WSC9YU`FkE)Vvj2iW`dYH+)ms`&}1lP z>n6uLHzNt>(swalOEe-S7zrm#v_92&2M&UR&E2BT*<-lKYPfjX>0`&m&!G0W4L{LV zFIi9ZTmpP)Za>ss1**Gu`X#)tf9)*a+}LP&@#nqIl}H*irNVh7 zf;Oh7^6|o_yejWHFd(isWGsm>H%xr}mdjYh8Pp*)O9vlDlhKd3%*{|yI=cnVqLVwh zABL%Ecfa0fN5{ZFU%tD*rrwWCQA)pCoaVedl~4csPtb5uW`)V4D`E)X_qXY(lhc9`dew3WKcJ@odbRAoB~DCKrIp9P1yCTR1D zIg}`ehjziHss)UjNMcE%ppcMM_sC*9C1gJhma6LN5yZ_>$R-mdsN@g)9fA*%W!hs;Ptk09 zFK@R-OAlOnbQsK~tc4ZYN@zE^vFu9?VwC{--8N<^!9*t(PQk!rp+Vr34T{?MY*?UR zA1Wod*LAfWy)kt2w9%XUzk3W5rH*$uw|UQ(GG~sV&yP?Fs^uC!AIwG?HI7Hh@p$#S z?iOC$S2^MncFLBP_fqrs?3ENY5<`I6S8#%XArlqsO9xFY_szV)Y@C^nBbhgG5b6LP z^G0ka_-XHy6H|M2_fMmZ%Km;4sj7PxJKunYF@dwB2ieiV!LTi@F>UQ%v##$~9RQB< zMmje31H+xE&Kyz5_7d6-N@P`t-HBL+|%)w?x&^8hm{kTnY} zIgAplD(kny`l|YSR8!K~)ug-pj{_SI7fVCw%|Tpm-3B)Ipwq2HJGFWkFjaWI=MmLExn8(R}gS zp42O8*c%smKE#VL7>sd!1qRcGACw9X-}i*4xPYtHy2dnX`~BlXP>N&{7-g&X2(jO1 zk}BEg02>B2c_u%8{FnxE_$P~m;01|$c!W+;{W_!pi-&`!`$|O-(0SGE+~?k5#}kkl ztnGO;%!4G&8>32zWQ=8VD}&T4ufJy>yLw>^4obY0j2TDdMl29k-bsYIVH~z z0Eb&7yA$h?0&R*%^{(b?FumGW%dSxzkK;0Wm<;iT8bW*vLV(|9XOVR!wowZWu zB1AdRrmS8kbT8CfTc3YGwh4I&r+3Bc^{J1C$%GV=KzW=fdq5KZwZ%V4W|Mx$xyj)$W>!ZfVUIJD&0?9`F)V#8K(V#({G}+13)KEI*z8{8ykp6La zklP@iQB2(m|C#IM9)IE~|L5yt-Cy+~Ha_aZ&0QbC2kmS3dbDz`)eQMmV}h%blM_vY zsp%^fQ<@nN{KBnis$8v$b^0sjZE2y>kx%?iSL{e>j@A0}_ECeGep zgq(z5Po#qktO0mIwoxxs17ujG$Rm8EHROo;PKi}-UcGvCuZZx|$B$nb-CwTAwV&=g z9L#k3G$E!mH?MY=dcEBrI(})f+oD?`{^o%Sv+utD-Ut&7XapUyz@-oZESwZW;HJne z^i#w*iS-cdix}7FJb+ja*zwKGMutX7&|SHt61OeK2VJ>FI*K^N+TlwUuT{ZR+qvy#>jezE!G{> zRqOw0{E+hVcp^K}%)!bK;?d@!4^>rUl!k=b1w`2`gaj_hy?3Z`=JOUc-3R|X7FirE z=j9*9Sk5i6=GZy6@vst@d+7WPoTbPrPsb6!0uC~L-oPz(O^IPiT}k54Ebzr z%*&j(Xn7e~9-kCTXw$v-sL|sKH{S0VA`fmkEa1ljpFO+dkcO(&xH{>@+GWJnWN3#Rb#&O))_D$7|V-(PoI?Tm_zYHOP+aE)e^78-jx%w7_q4 z;`Hm)#1SIiYrBwR0%Gn5d!v0&nGCI zI|Xj9LKT$m^V*KoQ3C?d0s;AyAs3JNzJCew@O<-LWBbyvu@F&V{gao@BDENZQmRg(DT|+Ud!dxO&o-XiDH5jg~0I1VdfWj zK*luz)oLYn=|Gna0z(=MIU@zy2Tq3jkRkpwlBO_}h-g(M7~TtyqfZB(y>%706a%Rk zcLfrE>ro~AaugzKZ>kDVX`sF(_|gKkASM}l7|jXLaOq2?4AK7!)ccr5^w8MV1lF#c zGPzCZ+BMgd)?`}lcbLJV`!dt*_MeER0JAScLpt_ydAaB&xXELrtOlm7eu^La$99az zJ`(^oZH8C3$MC&-L5InmoNpRJ*WPO8?>8tP(?HXcJOVX`t z6~*?4zSEEI=;%;rFM@C>g{+$}+Cm)7uQ_HQw%a{+eUz7j4l!Njl{#V)CU>~LrxL@X zeDin`852=3W^G>TF3CQ1S1td?edn}Q+-6^xRoB%CXA~?(PC>H$%7`fKXoE%@x2HYh zsi~$bB0nnA!YzCvQCa=EAEH&tfm3~8p-&Be{(RS( z7QTRI1qIa!T?mBRhR~VY73efXT-sOL7;!0_d9C`c*ttF zzVfDvF)@5_U5FWlXfV*m{YG!}>wyexzpm1A78VvHgNn&+m&0&hc&6INb>buz#$(uh zGf=qvkU)j=jT_}AdS6yj0i+7JZuhW4nFuKWw>mGN31sd!{aWJ1IOzXf|4mTJ7of~STa=})IF zXB%Yq!!w)N-Hic)r}r5;w)XbQ2pNjtg@7I2Ja&E~{80;=rk=?`{u~pYUt$bXY|S3$ z@TU?b%K2K{#YQi>TNWnQ!?0z8&sTN4tWD*&9JoKY*%K#fkC6nTv$IFtnE$os1cTI# zc2Z>dc2*fJGwtHisr4>hU!LY;vm!2q{%*tUbSvp|{%GzOCX{X!ov zb@<6nyD{(caeJmSJ7a0Tw*C$~?SOt`l$lB&7{Vhg3Lh;K=FpWZYL`D&HFybh{dvVC z*;@pS09f&>@lObQfM_nq#>Q?uBUBIrxzja1kBmAedM*tu>*VO#E_1D4>~DMTmVZW{ zdtGH4Zg*nt_Dc@WI^R>`kd1dK`BN%i3c9SbLyHeI=# zm)P{66q?!$V-(KGJHb%kRwIf1qN8GNV$}(Asf+bR8(=X#h7tGjlD8^W-1p|_?qZbi z=qybK+#7n{>v2&-CHy_!I49IYzGi?Qq#L>C)OeHCfZl!Bbdyx-Y+s=}ih58?d<*c| zS{u}!B!|bhTr5GU5)=ZliHTBD`q#US^Mln)sYcLj8HqnsNmt`poZhzBnODb~0hm4m zU`Giwo7VF1zTTxj1-FH%93$v$4OxqjtzKSIX%JRT~1r{#i zVlYuDb6%z=P$^PBZ9_Aa&rGL5&Nua*??AL`eqOdNM?)sD$_6RCAH5rsLH9vpczM_E zy|Orst*V!1T4&saYG1!fi*H5ZI!ip;N+=DU}efdpbZ1;<3?c4q6|Si%pnEtPB8`RI?_=!eO5n4d2wJ zXR`;P2ZnT139v5>pPL$DfGqk9hH(Y(=#RKz2|oeqWz=i9r-vv(LN$yOoSwi%Q#2Cu zYl4_lLB77^e?@3_52HUPPSjW24_a|k2Mx>$5l3Drwx|N>^c{j0UXonrN`uIJoHw>= z6O}Ka^#Nwcu944&8TX|a3HamQy%!uB0u&@A_m&6zy|?fuy+j804vLOZq&^PH#%TC#nA=<20BovQulB`PBr zI1mDUHZ(L#mcvq?nk85sDCWs)QMr*JArPWoc+*PznrQZcZ(|I7%R*JsPhwM7Q{71)V+cd0~`j6(SXdFgm*ayu9U77@V1zsj-Y! z`BZ*;w@S-Sgwn!i2;vTnxn8PtXW+=jTdBQ!@0i8ne)Mn14WsjhRB8|($OVx>{uT$V z@;$yiJ)5u$blykJ$=y1Mp2A)m+VNpDwyiCYsGVjjeF$Lp-JhE{iMI8C>LF^rkTwMN z5>jYJy{UV8${FfS5bw978nEL!zINSfcalPKGvvwfh>9j^nZV}I4uc7igZ!Ai8!t88 zWFIY-FT9KE0d|&f%VhbITC`2CCd&1_(>ug!o}Z%pzjl!Mw;eR2Jw&Ys8u2`?W$>51 zxl_3N&H5Tajqau71ums==#1nAsmna~K0j12_*7bJsy~S8rBk6CS!hi#`>OW|-L3PbRY$IF_mG9LH)>X{=UME9AbB1+( zcJ?{a4-J4il?48$usT<5P+SI54ENgR%^uo@7AhZDo*XA`=M+IKypkR`D<#Me6P)Sw zJx0MnhSTsksbyqTA5z_ztIY)YC2c1()s-g!^hXhuZm!EJpVE3wl#qbPY2qHfI9ZY% zhoq^{mK+2{a%)K}5KB;_DHs>hn43X7bDOa{g{BzT#>-Ig=)m+C#=UT8&_kj8?B^k> z2&xm}QG)fMLs#@4_nD3_H^o(5>=n{$tNQ)-9fU9*Rg{4mADZwGU-{IJ9yl=gX?|vY zULKLxFHn_jb`3B~`cxpK3539vV2UAjHqJ{&tu)zJu6p$?+6M#AYw%Z6#a+94wIy5c zr_?z#2&AATAT%YKGZi!pUaEF2ih)b5)M-il)pY&Zhw%ozg|oPsyK^-`@GF4Iwh@U! zEfaeFcGjJ;ngC`K8m`jq65#CKz(WF74d8(`N3AScRU~(Q|&87F&mz;x;~eT_1Ofz+4qE@t$Ri zXu9TEjI!^31Z{%AwD%{#Wk`WFJeI{lf)GuJVxc3$i~zX@hOOtjuT|&B#Dk0ZaPmJU`FA66%2~Vm(f&* z6dMw5?Rh&Gr+!~?@i*jy&5I7FPMsp))6fsg|4P0b=unNFuw6tUaULieI6%B*1hVR~+|=4!tjEAj1C2r+E{Is1IUY3eq5| zpTB~^>~k_HRCJV-th**g0Xs%*k{*1GiAKz{8c|>RbiLX#juRh80n$U&Sliv*ZQO|^ z=0gHZ!QMos;BOy@`>7Wk%dJZ zO~1;rva%^;L;)UoVJfZ)YvYg4VkdgLoi49~D_Ml$F|9e@ufJYJSmdNDjMV%~jvV?R zr;^P(-d?g+2}bU+*fHrfB{q(VP>gX%SwSuEGun-*&zG#9Og{(ufbl7VNS}>}e1arz zy7P9`b^{$1QbPN%HL##5DJrk77B3bwT}r3~>V1Tk)+FSiI}(YM+wB{1Ek-2oA+Dod zjH(;&4xniv-jA^&tp-V(d02$S2k%gHu$e{b4r-QfZ(!9#q|AcOk zTTL3U4U3SKSXEL2^BKAjbR3f+YtiYqxA7%-A8`Ey#vwJ8C_|bovdKjHIR=5S(qn;m zfkRb@0%uA$te8?C9@y?{G$*<>3DBw77jU`N0b~a8aaF^c!^i@1o@m@8E)MiD+K~3_ z+)YJqy0ZNlY{?UE@4A3#YNNrgLzxlD?l2?|M3-Oc93;@G1b4G2C*i|qJ8@X78NZfc z<#@%rce%RN!AeADHY9#N+L74QME8=?p0(5CkC~!wt~u);SB70bIqmbUqh8k$&O_Qj zOsDVO=hPcxw&hv|RZaVarD5z4`~DNLD8hJp?2yUst=hcF3Gj#dJ4#Ac5E^HeE4WLx zsED3F%nQh3X+#M^bYuV-O6a~vMKAap4h7#v`M;g*=tYq&+^@P0h?ss%2B{>Y82D`7 zO~W)q<}d+!A4NyV1r|s$i|&_P>(Tv0r6r8PIq904>02v9k2J*c zrouS&o@lRvt4oJB^#@B;L&GGv&*~SNVnFxR25w5;l{j7wpY88y01kII0KKGJQSXV8aXnX`2tle~9Iu85o z$?e4GHdaBwo-Fi#RoUR<>FVJWpx-72!wT*wm)oNY3?fyT_N|6+zD%&QiM9E{n+x(4 zbI+drY}U7t+FG>)^w|W`7SOT*+NIthg(q1i;%hR*+`4>`g%uAtgWp{7Ui4ziyQUND?p2Fr$FJ6 zC=d*@-;M=ph6Ar!C)AN+=K>Lfyl6P~1j&=Zhfm`usAn)Z*R|Y=RB+Pd!S^U2-L#$R zQ3brvhDIAX$t}!=d->?m59jX!|FT)S__`bl`4;Z|IfU-pnws128Ikg+Sb%N{T@pG~ z+Mma1jI4U85pw>0wK&}RAsbSDXCzl{LKYSB*-k@Uo#+rIHqMiy=hiM)IV_Cb`bL{k ze_(qlu%`ug%HCjnT;}oVP;K?j%~wq&(}VhrKZ&KN7x`(gLUP+nG5jP!)VXKJRHM6f_Jdf~}6_4R?LOCkKn@m^+~bU<4E z669m2xu~32iwwqComJr_+=t6H>Ld42?KUA`l1k%`VMw;m&<@eYu-vIHQ^L zh}t)8S0RT&Fz-*v#$CA)dElfcdy0sxUa6cQ9Knu=%g87@ITa&MRDf?p_}dPzi9`@$ ztn?W8fkOdene8o;+R{h6Y3=>s%+yB`n+6Tl0*_cyL1-s^GG9HFCQ zlaXmm?b38}D^sM0YnU-;1x%-7$=HR#?Wt=kL%y}Iw}|l-I7|7CLM4-~6_#**^IFT-B_J}qN z?a^bCnEuE6!yL!=qCieXda5>JD-*SG_Sgl;WEl&oCx}6J%}8|9g&+Q+vDLrw;<^Z{ zkdOxDe!6GqwL%B?*ZkWz+pY{-#5>(^R4mw1Ze84Pfg``&1Kl#rl@7b0{TiZ$GiVPW z_WaqiNsWM%K&7HoZ`0l<{h=6?*QmlY_pXbfPG14FxdEIl471jk25oEb{`B->lDjmG~87De( zJW2B+%RTc>!-3ncR&PVv0p=}Itq~zVsij3;Ij{gb$7aPDEvZigG!T%EC6*%tw3|-> ze*>jR5|DMZi6w|%5w>zu`8_cG@&%)W+bhQ?jCDlIq9GmCNv!vJUE|VM2+z-v; z_5B%iX>>=ADrF2|Q+S4D&T z2Kzo7CDjRb1SO(@#LR6Vi*520ZuWef0_SV(Tp0;&avEz|itY1U)+212Lk!zl>pPwx zt6*eg3@naHOJnoA!UFqGuU&II#)q{MDDL}hQbAC3h6p8~`s6h=qY(b+c1z?gHhm(O zYITCsvn8jyn$g?ex^3S?d6_PcH42X$vG~=^?lRXBe0WMi1vA14*ek2sO=GSY#1CzO zP%=bQb|QU{YV`-0L?ZT)a5POv9OdNX{&c6XUl`+nl#IMje(PU-W3rkzvMDHPMxTqG zIcl$D-3C-5*P_3S^Bh1klXTUb596!o=_+4Cmt{@vQ)(`K!Ubhu1=BIOm;#);QKU??PxC?(K9=I6jY6GJf?HWx)s*^Ro(AgC zq7{>-cCQqED%>BLyj2^Z0Zfl892t z+ApJ{CZFGn?e~4jUqI+Lit0vPaa9c$o4c=17KoQ;mSOyg(ArqsusPF(@cOmo^8QcI zTx6(rUaqz}DEF4Mlwu^7Y!+7|A1~WwZ_oU(IFqw25+|QQu{?%VDtY}$TWfQBIx`5rKdgI28WZMZ=kv?MyX=$IerP9_B3mi7} zDJspw>CUiM8RnmE|H=97zIuJ@q6b&Y=6yDOS$lxx9&y_XWSNAyCNi3r@ecp~;uG3Q zvGejJO!Se1(SWSJeuDfwgP_Ar)c5D-?`OV#?E-L3VSBxN+p5si*f<3<$Zx?Lp>pb& z&k0+)4Jb{adfL>Z|G|_uoaloze*ftCk)Q$`I7MjTLW4_S6@wiF(R_ZOV<$EaUM{!p zLHT57sdE7uwO`;W3Ucg(^ezR-A&h03Z5=HXDjgl>suOJ>88g238uzN5Knwgx_=>63 zr-xgBnzew4w4%%b1`r#CJ5QYXo5fPk@f7*+>x$S)=wdCM=;W%_zJnnSO@bIy^&1k& z+`3&F`*uH=dOd$WvA_|6MI`2g$%FkxEn9Oc2*g93mL!`|z($0*V=opXyu@F$#oB2lA4=aatxYzs4irIS|m>Nxbv(y`p29weg~QH=hQx5J{eg7}UB%N;Cs><8hcFj4V4N4cIx|J)s#!#KjaJWp%X$hA{0u$A zzL$cLH4%YP=Dp(tEGRB%nt>d?`@|bO1EOh78GA$to4)=^euIsY^KhW@VcPD?uonmu zbj&7>zANs*HHE>|s5|*A%)Y-5VpJBc7_D)lGO#J>&^EZ0(+N)+A48lq zq{X3_h&?dlQ0vr3q%$ikw{7}e=%tD#*VNqRLPd`c-cQe|{zhh)#3g$msfbte}q$0Ymk#>ZL zpZj(=0`jJGo$udv+%4aw!!8>X$1i1ToRd_;d(Pl18e;3i7>Sena<@TaZSA+Fg*4R{ zM3-&DIhugy&tvuL4=y%Kuy>j%bA;D-72b=3FB!_&{g?7aYK*B^EM}5eA;3}wbKC+) zj;|ThxbvNGm9^%X@C}cQbnV?xQKL1zal_-_!7xL}k)#W{;}fIvt^4^im_Tw%aBwUx z;*w%Lb*f_UHR519>Xzj9??;e_2Uj?zrf3W=_w%s|Zyhbgp%j;rI%m5T5kUtZs6^wA z3(^cOGunSnRsp*pf+@ySI>MRh!{BS;)L|G-2KIn4a&GDL@{~q-z5L9J&g@E~t|iTg zSFu&}&OO=}9sm&W|s zc*}T#x%Xbx&y1(JoA8&T3NiI4#)txGbDioFX7mg%Y$=RvSK)wWhz_TVDl2Wi6L2KHoF@|3eb9NJj-&vlUpl(giP^8|ZsUgv;wvHyU5%&RB z0yy;3iZyR+1(b$FJ7$V@#l_Tt>3+BMNb{nXuaNXelvI8BO37jBIQG^PSC0&e{_5oW zf1+}11NhOJp|LI}k|Y197NcE_q4dR&PlrwUoPW%k2_YQe3^Qy0`#YR0)H|=16$zy6og1--~og@`Xww zG>OgHT;Cl{$1~AqvL;E8o&Wh7P~-Y;_wLzf>wz;z7#OrRmVrN3KTB1JyRmO^s#gNI zkH4>Prk}rm9?TnwJ71j~enFd%cBD0zuY8t3GegT@HlE5WT;4i?m{piLvDm)dKJNO% zPd~{cp*eg+Q55JpzxA1Gh*uiuUS-YmziX?_O@j}AyEXl`3@S2Cnx|capxd zH6I}zCE0`rPT&aV(Zpc>Ywo^VjcNNO&nP3a&O}ow77`MqpAcRAvFKK<3~{fYPf#nz z6)%;ioFA<+Sz-CZ z0(z;lv$LzjKH6@oCl;53maTwEQ_)jji_?63_6WNqq<93a;JM=f=#TpoKKXy^uM3L~ zQ2bo@c-HsDgc;E`x44)mt_8`}RH4qJ-eCgswIv^gQI%IC=_IH zbn`>O3kndHduXrCUQ0(+p;m13xIRV=m&C70M=g)+-cOeVZK4JQoW=T$TUVfr$)Vj~ z=i-|8pxP3>^vzeVP?P%UZq9#^I{*1a*w*!he&18b3K2LHv>oB~{Z7kM7~*I*j#9AZ zzs=8SkLJe2E>;&ohm1EPd(vU%w$ z-|yP@?x5Ha6GqE7108`X5r#=aK5x;Mm;=!t2>g?AO=lmxCEW2? z85yr*x3;4-5Lzo4M#=`jrd&mOrS9{!x|zc2PbzKfZ|nfM=n|Ic6DXSw`87wDgVK#>Bc zkpHzO|M?e5JMozR`LF-+P0Wu-HUG!o|MOS>|4sb86#uWR#C=wTif>;rHzg(0{QS(% zZI!$@r@7&O+NkNps_tLjBA0otb^rC?-_$={I{Ej9OxgX8o-U8Se#+eZ?~_15A!|zn-fv&`Q^wvnzYGacL1Z}1N&oHdVrc#q zt=IqAc#2*W_y78#H&PkOnQn?*zZc-Wf92^U{37wS_1ph^vr+$i)B$m?W83jxzLbhf z_bCsOr&?2^{ruLB$}lnZrhNJ0?mzz5>$HAncI~h4`1ra!|N4%CBFTk7jlO*q7HM=Q zEBPVQ&|*H}z{q^bk)8Ul23XmrpLqW|N|x4fu7AY=M3R(I1gpa5$DiIPoq4yd5?{Kx zL>3pjbY(=n3N}LusX+#_3#bnR{QY(8-Kc!ktnBUCqlBH8yipCME^-Jj_lOKntc~id zda(<`Z_N4PDuv}Pqffs`FZXceJMaq#+;-ny=i+PO2$MX6j?IhyG8wUd5=xxGzx!$ zeN*-Eo&`VuWqiD70O!?v6`qv2S}3nCMLtP{A|np2Y|p%l8dg_UYS6&srywgEgEX!y zvfYroroBDWfuD&<6i83jC5xLkL(q?x`s|skqx1Sxagptig~xXI`wzSdc6RwF8YP7`_(woPqC>Ln zGc|HrcX_)dMsxkBVG8qKa`z3Y&Cbumork`Xgm+-sQ8r*Gbn8|V?9=?cyu^af>y3Zy z+&kG1gTp ztxe~Y+};mJ#RLWA@HX%Yvc?m8&_OHUzB5 z;^sIs8_%9y@XFoTaEas(yc&!Bd7n%(`JatVL7{#F*9Kn%>#lM!IiT|&1IRrygK+`R zKIJ;B!Gs58sPbgP_WMKbfr+EhkzB{H*C#1?HHymfGju54Je~28BfW4bZ|b{MY(JeY z>}u{0sPgn=U!FXJ`&@&qJsm{3M@N&dJXi_0x^Gi`zXs6f(3$g;s9=h~`^bojR^d)r z5rGfKGaK>|_gzO;1()+2d{);f{g~S?!4*(wzOJ9GS<`)UdmK-LcN9wOKo6?&M2F&- zn9GolXNOq~ywRTc6r0mKxtr4SC?$oBy?wzCQ$XpHz;`L)%1{1G?(YA*wzRl79C`fy zA+v*gQ`=GiOEWoiJ{)6^*B`lre5{{^d*;W*nG`sW8b7CkryerElN7%+T2b)*(&b@! zPBP+R>kEqyeWfLfgPs%(p_Z3ECJ&-k{_dpa!1t#v>_y;^`-0A$tnf|s8oifGvjQ@c4)oy1=~#5fX*xW z`e>2cP|%#KT7Th%(3E1*e%kBKpNygP`aqN~!@N%``O3TQT%%0lB%*`*>r@pOZipb+ z;B(w&WfrGb%RAA7+Hh7z z5lA0eGy8u5`P-sj+G@&c0<$i)LQC_a42Ih^%}kSOJ<;}kmtZQ8mvyrim^VpACRP@i z&>LId|6T|`pjTm-I3F;nWxjavw<}j-akw=^wgvkKIh&en;Rl~EF!1y0>i33IJ=0#4 z2P$|W`0llm_~UvPgS+XykSa!j7v`PW)y8EpF?as-6vauodh+C87~iz>%f!UzgM1tr zdnoi=h5!8XIz<0CW6uc|4t7m8q!EGyX^e6`x10Yx&l6=s!$LVZY0J0jo^|?Mp%gK4 z8e4wL<<&o^!@@B&6K~>sN>Aorevw6}vQi0NSXhv=vC$y@4%MWQk&#;Qf&pjt;Yfky z+>nO)`aIf=>A4Ldwgf5$VM87n7neUO2Hf-8R1a@tj-6Op>h_$NEYKSIZby<_u8!J7 zyG6|PJ3m-hFu;I!vG^yu8$uB-6!*jTnO#dVo|@vB#I%L?x+$?IuT9C#kEBP0iQxP~ z+E+~R)9Fo@uM@KVn#eKjrJA~m5?YM>HV^=38C*#mbkPM3`Sl2>P zq#z#Z%f!hku2UtQMf)-9rh~)7sUib!*q0|Zinwc`=sOaWh%MO)_^sBEo4>BHu+RY{ zs!3j*UsJmha$K7;S~sa_yFan45N2(br(*xPBT>=pyj-+6DR3D{`MD@*u}iAP z6mMpS5>>wYfQc7EJ18WYUR*cvYKYX&QAP86u7G=cMk!kY7bj;xkX?;&S+aa|UWn$a z&&U2K?fp`Ch9cm4+>L@UP+kQN!o77kVS1dZ%Ipv>?jgFu%>NHvUjbHiyJe4}fFK|e z5+VwM2m&G{Eh?a-bc1wvw}ME6f^HhEY-I+Tx|7Yf2AFqD*({s-6 zyzh>+)?V8HmzQ-^%jx8AGtPR*WaWYMa`&WPdQWWILzJKRAj;Bbu5sL>soHaLc2>cz zRj+j^7<7SNpi~5DA358I4k$S1`#vxbkM9^6d6{(rQ~I2oD4F*+Bf}dSD0BKeJYH9P zfY10*^UqIBUG)|)2ax1F2!V9pUTp$LPCV*win{YAMSD+=^~*ePv;ZtC^#wXD1hHZ0 z%W`3t`#*Unjsqw{*Rv`0TwQUR&@?f}zPaRE-QY)4bAExkUyp7c&Sidaip}bB%)2<& z8qP4@K3MWquk$$Nb#Cg+%m?1f<9?zGL~K^o7vsJZLuk1WV2)KcTWw}$=5)TR-)G)IpI0rL#--)pkU}Hjk|fqD1E?Upo(O1NUT3t<1sra28~*t@tE-%*QWlTA ze$Wo9Z*6k*Ly(wEpb2eD;{P9=q?s79VEPaTk$+N)i_;M?nWtG*UJmW3%CkQCJaq}0 z>nI=E`uZf4u(xR8p$3t=tJ=3(6`@;01@+5pZ75I*GB}YTI9e5QyGJm>Ewr|>DmEU* z$vP21+vc|Th=_NwDx7j$c4yOWc@oV$kMCdK*VYCmzq@DKWM;p4mg9))4w{C%(T8#` z!UASV-tt{X`J}ZuZjZcv?bQI=&zqoK0a*P9Y+_5?F3iUYbwefML`qK1v2Wk*t4VGN ztBkAn+%6_j&4#1{mO+0GH&u}C+Q40@bZNh+Fgibey4Xzy!3=x~mBDFXQTg&SI?dSa z%ZQkmEOPG4KOl$&1&ukE)4osFBHU9TY|L=VW>;5JL7qE)p0mQ!2{9ki1hPypjrRxm z7)=c>3ZY}xKcQXOp?8@mg)HUg99ogm_NYI64`r`RR^VD z#Bo|I_^~zw#z6nMYeHF$Vl<&p{XFX%%03xf`nW{tPt+yz)**$IH9!V33Ser9x*cvy zH?GyO9gga6ufl^yRzN_xo^F;6aydMYa)OnJb>ego-r>>+2t0!F8Ahha0o2o2Isi;W zgqgWOeigjBhhfKp@87>x41JLQY^PmupS~z86?9Tfb`TUE?guMb6SF6DbjmQBR3rla zcty3Br!4-s7O;830E!Ny@lwy`YCb+bm|Gj(n&c)2(orV_P(`scQshfIIGC20PrnE3 zl2$>zwe`s_iMUGnRV<>;UOz&FVoA~Q89FjB!N2!K&8O=Kn(Jc8B!1jO0_Oc@%C^$5m2(mh_^j7; zstk4e+DL(avpI-j8QAI+U%zGnBBO>dd>RsTnaJ7s9xiOfV2|0;*NGh>Gd~YblXMq= zNJ4ZnnI?`7LzYcTR#H+2*?qA3#1=|Cg*r3-%S68jrndkEI8pz-)KuP!|g zChb|;i%s=UQ{)yel$v1I4)BACul}hlz5lDSY;S!B3-&VM3_nBMzksm5(Y+4k3e<7_ ztSYG0|DXtjRPgbkEcpYGkq}R67h3YRkKaHxhHQnw_gCQBwa+>{7WgpLkQ30te-)?+ ztoyim)@Ju~iE&7?jgymd1-k7&0+IVz7#Om{xx9vC4cO_LjSgAC=sFN0nGnyO*^Zhc z;u0q&RQ-#0b}&H8Hxhf*US|jL!1?3VVE)oE z>`Mi(R(b_wa8MUdSu1R;(t^jE1JRcdc^EfFrh`~4y5=QsqMt{HcQ z(~TnB>^`I6ph8Y_Pqe^nJ~PX|3%6i+Mc-~1;dFor@@Ey_!v3E@( zfaY|zB)_X9_;!?X@xvq&d?_IU$B$RRGOZBvQsFVQ<;d=vj+Qq6oq2Ck$za9PK)9^A z%@>X!?rQ7m%J~OT0FEW`BN__aQo-qeBR{z~NaQE11-`C8(n(4Ris=_jDiFayYpRk0 zh1>6~b10qjBfHz$dZAVbtlerx)DJpd*khX8|Zj0JG0Izrf>%}2n#zClY(f(rAgRSp{`0+F|@#0KXtM0bu-vofR8F+x?K0!Ro;*qwK7al zOe|Jz4D7zj1{R@0fHzk=XgLZipU|_4Z(`|1IEi;AT-C@aYiWskgA32Zz#6&LY(oW3 z1oA6R65Pw~8!z2dTsnt_qp;l@+E-(UbvO~+0D;i`NrCcDOPh?V26-LS{LfhU_yl%$ z0~z{2uFVUjhSYO7oIl5sw*QTVDYS+?L$P3i5R?XOVg{5{Aj$?3Aq8S7G)$`_1ysQD za<;cu8d#4`)trH8+{)Z)2st|Hbq`j%jT_$MOry;C_S@qFxoS`Riy;q`9EVs2KX3(Z zu4Zl(QjZk0p`kX8AFbgZ43t~SKp2DePaF~s{D%u_FZt}_)Bz^(2%uwOH>wK!LD*e@ z`Y+`D7bsxV%D-g<1oSH-+ycSUdnI3HezOc$&KoDrKiF}#uUW$WFf1Fv8W$g*#-jgX zqg&%|M<*FaF8w7pq;x>1tjlYzp?+NfYKzEl+LhxC;OZq+VyY7iMw69sNy+PNlap_9 zb0g3Tmlqbm_g@WOrv#V-nGDn%sK24Pb%qt7zrb-0X^r;yDL>*BaE${kfe6F(q3O7_ z+hgfHrhWn0+I!0MmG*P<_#jiIhUW+OJB~jggF%Jes4YaWy{D`u5DX(=S-}=21_siu zAM!i6{3)fT#Qy0^<&QWa4SNh=1z6`w-MZxq(;o`ZoNjYzntvqk;1C#Z8%XX*T`t&Z{a8<6q84kDFULz5|tOG+@y`RzeRlmS^Dq zD;P+6F{;S{mln*LzxTJoxh7*X)*F>N>tNysWk+fMx=&DU76|j6($L65Rp0Cw0n!h0 z?oz{MKMiDSq^BOo>kJC;Zi`bMAl{G`Vi92M1)YfQbK)OyuJ0jI$cwaFBKHG;G?;$) zU^-v38iYMrP6t{GTk0}2q3@y2g!CB;C%<=hUKpI~t+Mu30@^DPsMoK92QpmmTWC%|I5wM@6sJjY7bf2E15{GR6j0HaGtn`4m6F{)Q1M{^C1wl=}i(s%L| zNjLmIy=wLs%}Ffhkc?zvalw5-L}V=;dPx7}jSQI#D4_F+7-O@L#l=)^o9r7)Yx|aHH*aQ1CR_uLns88Y0s{?i z9S(yuDA^t`s2mo-X)tAUZY8&G>*+x_{L@XM3&Xyo^&sx8!gu4tj$YzV+wfaykA4+s-n?ddQM{e?e{Y3*H2X4iG2k%buRcZ z)3F`sMCB#Yh~FK|kl_Fji?6=O}KqJM-c-3)sdiTMD3?K=!AZ36t2E7}97ldmlxIohUg8XGZ;#kS4 zhgX{+{Awk+EYFcIOdba&QN-ajLK8GTiO~$tZKkEC8+Jt#0_z+%>0FXIWh3w#!8*r> zh;Cj&Vgk7D+jII(fHx!W75tS$5TtAlH%O}1caCuXPzCcmf{h;Ur2(l1^pXN+++L)% zDE$5p*ae_fwO3}?U!Szu1wh+mZ9SdixnO1?IHCK2eVf- z?ZLyqZSgrSUGha*b!H=XL0(=0?5<1(-#el*&gd>Z&$$puh{5P-5^}dw+bn-DX}`U#XhZ701)jp$h#T3D8V-&K`nLTf4XPM~D@4 z;;!2kQP9mirCaN{>MJB6zFj$6UDNK%>*2e8W z0g}OR=`IHcCN(t<3`(8%_j>j^1{vo8Fl9I8QY>j+SXfvA=9L=ncXd&I<#?d|QeKKcu81`3+m2}0a5E1T{KgIS6s?(O@!y*R_=miVp!B{l7`IEMX*r5tx!vDxQLWUmN1Q8Caml`7Z14_$)OR8Zv> z+E&teRd*W;tBmw`bA4SO4+T`HL2zWA#$R3>9e`s{BtWR?NW^C?*5oogFK1=Y7BIpaiE|sGqqE-v(GccJC_Wpw3qCIsAXaF zC$vqrF_S7aS-=yUw22Vcj;>IvnW@?R?D#m`$qe4FN~)v7Kq2QlqGT!Z%E z!&l&VQMnj?1E%7#mO7(W_djI=o@lW02ai2Z1|Tlj?wMgeeYrMXhTIXD6U^t-F&Cft z%ebL2IUE#R2EVQ@)hm9Fsfv$=gxnrYj%nrak$x0e*s(*Jz zL_}G2bu}S;M0mr4)sFl>xG=`?pq|SL+DDq-b#z!C#|HXkCWFok*qxlqy@cSn+9{@K z7G|(_s+QwqQo|X;BX;1NCS~>M2iAto{Sr47=e7?Z6{-^n%ap*1BGHp^a|&2kS~cR5 zD=0X~P%=S#;tWUvn1SzWLtO9GxcvL6neW1}#<{(}s20g-*lluwl4rV*q!{GT?9HEl zKYq%=F|3JF2bxlq%{(}wjVPZW4}fgZ==!1Lyuo z9yF_9h922|e(Az6-3UkKV5c!y^(>j%{@Ku`skpLAYS0P(Eg>l=ZZ$Ef&R7_^O*bBdD(GoGO)eBL!RuFxYT zkBoeUGnS0(tg1f0HX$G1nWch6*}lYPQ^B(j(=?`l?+QX8V?+G&PFUU>?_8pO{rDBBPKp~p}aJl+CI5YBhh+35X z%A_VjjRfU6Wm2i28YTP;#xB$lE2u8E5!} zggLW~+XbU&deBe6u{Rpp{{U4J^3mp~aI9zL@s+?DLXkCNDR-ndR8M@_TGjUDyzJDE z`#vfJA4c3NbWRu%_S^WeWOuUDMkG5)_UEV8nze?!wgz-9}p7+k{1qvL% zSPTNOVulxxyrRr#q#qD$o;_J*x5m%HXO|gRkhwNHc;Jq$0G8slEkUj$YY!NV=zaYC z72wGrOVIR$T7}$KJ^&!YZT9FgMOvi+WC5905*BI?KS`+00u=r!b3v-0^E^zS+YqDd zDD(Aattmag!^=P((9;A7DRn33Gf>epA`!ttsB1RMu6lt01<_x~kzOyiNse;7)A4NU zGmm~%rK3CzetG;Se2Fk5ycQ}sRf2)Zsq-5H%Kh%8tNUQ;4clCEhH|)9l7pw{aPF?F zUX=QqAzXdrloVwjR90P`@Nl{u{{*dK?r}10T~{_pX8tKB0Fc;5nCG8W02qM=R%9Vksr4O|0oq_F#Ndmpz6dzW?x2SXLSkFnPo78!t+qaG!-(Su?roG@7t; zpKD~>l6Mux&4D#J`xLj4@5-DjP2PaX5o!rn|KSXNNn_0ysB@6yrqFn3AZ`8f|c zzguS#J1`932W+WBbcowa{m2l5rxomHoO((9-1T$Derjll#H!IS7jFAw#A$9M(_szv@rp zHMjq%^d8!0NRBY^axMpKafeOLq}}*OWXUFT)a{8YA?^r^{r$6GnOQEpb*>4171Tgc^~S?5-onbZ zDfV{Hk?YWrTAP^qD-*zr{GlY5F&dQ$sd5(Q-T$M5O=4BnzuX3rAOHn_!lbRzS~Z9O z{;>3%cdh;;h4+g5U9n+LTSv!w^u#oP1TYxuba_dT(s9)#snQ zV2N|@uWP7VfC+ojomYS*7)E-05ilfwR(f$^$F{sgTovFw2It4Zrf641%@D__geKA< z87jA|52?NJ4$3Dv&npD&$og(;LIM2xF$Dz$@8O7yyr(wxDr_Yu>MunzRej31MMT!K z`qBGnlc7uzZTfdtmo42lK|}CGgD4L(D@wR%WwvUMsSgH0UaPR?R8w6IP5^uC=)&Wb z3EQ=2Ohn{OnbNwo8U)yP%Lr}i!hyR9KdFI2S`fB8m64qHgo7|?9SBo-cmSE=d|@