From 2ec851fd34c6fa7b7c41d36c271222735e7521c6 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 20 Oct 2025 15:25:42 +0530 Subject: [PATCH] feat: add slug validator for sub org creation --- backend/src/ee/routes/v1/sub-org-router.ts | 7 ++++--- backend/src/ee/services/sub-org/sub-org-service.ts | 12 ++++++++++-- backend/src/server/plugins/auth/inject-identity.ts | 4 ++++ .../components/NavBar/NewSubOrganizationForm.tsx | 3 +-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/backend/src/ee/routes/v1/sub-org-router.ts b/backend/src/ee/routes/v1/sub-org-router.ts index aed2b63d6..0a03c2ade 100644 --- a/backend/src/ee/routes/v1/sub-org-router.ts +++ b/backend/src/ee/routes/v1/sub-org-router.ts @@ -6,6 +6,7 @@ import { ApiDocsTags, SUB_ORGANIZATIONS } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { GenericResourceNameSchema } from "@app/server/lib/schemas"; const sanitiziedSubOrganizationSchema = OrganizationsSchema.pick({ id: true, @@ -32,7 +33,7 @@ export const registerSubOrgRouter = async (server: FastifyZodProvider) => { } ], body: z.object({ - name: z.string().trim().describe(SUB_ORGANIZATIONS.CREATE.name) + name: GenericResourceNameSchema.describe(SUB_ORGANIZATIONS.CREATE.name) }), response: { 200: z.object({ @@ -40,7 +41,7 @@ export const registerSubOrgRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const { organization } = await server.services.subOrganization.createSubOrg({ name: req.body.name, @@ -100,7 +101,7 @@ export const registerSubOrgRouter = async (server: FastifyZodProvider) => { }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { const { organizations } = await server.services.subOrganization.listSubOrgs({ permissionActor: { diff --git a/backend/src/ee/services/sub-org/sub-org-service.ts b/backend/src/ee/services/sub-org/sub-org-service.ts index 8bb6da13d..e2433a644 100644 --- a/backend/src/ee/services/sub-org/sub-org-service.ts +++ b/backend/src/ee/services/sub-org/sub-org-service.ts @@ -47,13 +47,21 @@ export const subOrgServiceFactory = ({ const orgLicensePlan = await licenseService.getPlan(permissionActor.rootOrgId); if (!orgLicensePlan.subOrganization) { throw new BadRequestError({ - message: "Child organization creation failed. Please upgrade your instance to Infisical's Enterprise plan." + message: "Sub-organization creation failed. Please upgrade your instance to Infisical's Enterprise plan." }); } + const existingSubOrg = await orgDAL.find({ + parentOrgId: permissionActor.orgId, + name + }); + if (existingSubOrg) { + throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` }); + } + const organization = await orgDAL.transaction(async (tx) => { const org = await orgDAL.create( - { name, slug: name, rootOrgId: permissionActor.orgId, parentOrgId: permissionActor.orgId }, + { name, slug: name, rootOrgId: permissionActor.rootOrgId, parentOrgId: permissionActor.orgId }, tx ); const membership = await membershipDAL.create( diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index bde9be050..9a959c5bc 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -11,6 +11,7 @@ import { BadRequestError } from "@app/lib/errors"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; +import { GenericResourceNameSchema } from "@app/server/lib/schemas"; export type TAuthMode = | { @@ -147,6 +148,9 @@ export const injectIdentity = fp( if (!authMode) return; const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined; + if (subOrganizationSelector) { + await GenericResourceNameSchema.parseAsync(subOrganizationSelector); + } switch (authMode) { case AuthMode.JWT: { diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx index c05c5abaa..5a736491d 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx @@ -26,8 +26,7 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => { formState: { isSubmitting } } = useForm({ defaultValues: { - name: "", - invitees: [] + name: "" }, resolver: zodResolver(AddOrgSchema) });