From 63279280fd048657f1e06d32399e5346a65bafe0 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 1 May 2025 20:41:30 -0300 Subject: [PATCH 1/4] Add Github SSO users to default organization on signup --- backend/src/server/lib/schemas.ts | 20 ++++++++++ backend/src/server/routes/index.ts | 1 + backend/src/server/routes/v1/sso-router.ts | 7 +++- backend/src/server/routes/v3/signup-router.ts | 39 ++++++++++--------- .../src/services/auth/auth-login-service.ts | 31 ++++++++++++++- .../src/services/auth/auth-signup-service.ts | 28 ++++++++----- backend/src/services/auth/auth-signup-type.ts | 3 +- frontend/src/hooks/api/auth/types.ts | 1 + .../pages/admin/OverviewPage/OverviewPage.tsx | 2 +- .../auth/SignUpSsoPage/SignUpSsoPage.tsx | 2 + .../UserInfoSSOStep/UserInfoSSOStep.tsx | 13 ++++--- .../src/pages/auth/SignUpSsoPage/route.tsx | 3 +- 12 files changed, 112 insertions(+), 38 deletions(-) diff --git a/backend/src/server/lib/schemas.ts b/backend/src/server/lib/schemas.ts index 9f93eaea0..72ee8eb4f 100644 --- a/backend/src/server/lib/schemas.ts +++ b/backend/src/server/lib/schemas.ts @@ -48,3 +48,23 @@ export const SecretNameSchema = BaseSecretNameSchema.refine( ) .refine((el) => !el.includes(":"), "Secret name cannot contain colon.") .refine((el) => !el.includes("/"), "Secret name cannot contain forward slash."); + +const DefaultOrgSchema = z.object({ + useDefaultOrg: z.literal(true), + organizationName: z.string().trim().optional() +}); + +const CustomOrgSchema = z.object({ + useDefaultOrg: z.literal(false), + organizationName: GenericResourceNameSchema +}); + +export const OrganizationInputSchema = z.preprocess( + (data) => { + if (typeof data === "object" && data && "useDefaultOrg" in data === false) { + return { ...data, useDefaultOrg: false }; + } + return data; + }, + z.discriminatedUnion("useDefaultOrg", [DefaultOrgSchema, CustomOrgSchema]) +); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index a71a69c20..cfd6ca84a 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -623,6 +623,7 @@ export const registerRoutes = async ( tokenService, orgDAL, totpService, + orgMembershipDAL, auditLogService }); const passwordService = authPaswordServiceFactory({ diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index f7a1b973a..5f66a67ca 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -23,6 +23,7 @@ import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github"; import { authRateLimit } from "@app/server/config/rateLimiter"; import { AuthMethod } from "@app/services/auth/auth-type"; import { OrgAuthMethod } from "@app/services/org/org-types"; +import { getServerCfg } from "@app/services/super-admin/super-admin-service"; export const registerSsoRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); @@ -342,8 +343,12 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { }` ); } + + const serverCfg = await getServerCfg(); return res.redirect( - `${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}` + `${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${ + serverCfg.defaultAuthOrgId ? `&defaultOrgAllowed=true` : "" + }` ); } }); diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index d9196dc88..8c786355d 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -4,7 +4,7 @@ import { UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { ForbiddenRequestError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; -import { GenericResourceNameSchema } from "@app/server/lib/schemas"; +import { OrganizationInputSchema } from "@app/server/lib/schemas"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -88,24 +88,25 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { rateLimit: authRateLimit }, schema: { - body: z.object({ - email: z.string().trim(), - firstName: z.string().trim(), - lastName: z.string().trim().optional(), - protectedKey: z.string().trim(), - protectedKeyIV: z.string().trim(), - protectedKeyTag: z.string().trim(), - publicKey: z.string().trim(), - encryptedPrivateKey: z.string().trim(), - encryptedPrivateKeyIV: z.string().trim(), - encryptedPrivateKeyTag: z.string().trim(), - salt: z.string().trim(), - verifier: z.string().trim(), - organizationName: GenericResourceNameSchema, - providerAuthToken: z.string().trim().optional().nullish(), - attributionSource: z.string().trim().optional(), - password: z.string() - }), + body: z + .object({ + email: z.string().trim(), + firstName: z.string().trim(), + lastName: z.string().trim().optional(), + protectedKey: z.string().trim(), + protectedKeyIV: z.string().trim(), + protectedKeyTag: z.string().trim(), + publicKey: z.string().trim(), + encryptedPrivateKey: z.string().trim(), + encryptedPrivateKeyIV: z.string().trim(), + encryptedPrivateKeyTag: z.string().trim(), + salt: z.string().trim(), + verifier: z.string().trim(), + providerAuthToken: z.string().trim().optional().nullish(), + attributionSource: z.string().trim().optional(), + password: z.string() + }) + .and(OrganizationInputSchema), response: { 200: z.object({ message: z.string(), diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index d1b0a550d..6e45ba7e1 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import { Knex } from "knex"; -import { OrgMembershipRole, TUsers, UserDeviceSchema } from "@app/db/schemas"; +import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSchema } from "@app/db/schemas"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; @@ -20,6 +20,8 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; import { TOrgDALFactory } from "../org/org-dal"; +import { getDefaultOrgMembershipRole } from "../org/org-role-fns"; +import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { LoginMethod } from "../super-admin/super-admin-types"; import { TTotpServiceFactory } from "../totp/totp-service"; @@ -48,6 +50,7 @@ type TAuthLoginServiceFactoryDep = { smtpService: TSmtpService; totpService: Pick; auditLogService: Pick; + orgMembershipDAL: TOrgMembershipDALFactory; }; export type TAuthLoginFactory = ReturnType; @@ -56,6 +59,7 @@ export const authLoginServiceFactory = ({ tokenService, smtpService, orgDAL, + orgMembershipDAL, totpService, auditLogService }: TAuthLoginServiceFactoryDep) => { @@ -719,6 +723,31 @@ export const authLoginServiceFactory = ({ authMethods: [authMethod], isGhost: false }); + + if (authMethod === AuthMethod.GITHUB && serverCfg.defaultAuthOrgId) { + let orgId = ""; + const defaultOrg = await orgDAL.findOrgById(serverCfg.defaultAuthOrgId); + if (!defaultOrg) throw new BadRequestError({ message: "Failed to find default organization" }); + orgId = defaultOrg.id; + const [orgMembership] = await orgDAL.findMembership({ + [`${TableName.OrgMembership}.userId` as "userId"]: user.id, + [`${TableName.OrgMembership}.orgId` as "id"]: orgId + }); + + if (!orgMembership) { + const { role, roleId } = await getDefaultOrgMembershipRole(defaultOrg.defaultMembershipRole); + + await orgMembershipDAL.create({ + userId: user.id, + inviteEmail: email, + orgId, + role, + roleId, + status: OrgMembershipStatus.Accepted, + isActive: true + }); + } + } } else { const isLinkingRequired = !user?.authMethods?.includes(authMethod); if (isLinkingRequired) { diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 58ba9186e..f3205bc3a 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -9,7 +9,7 @@ import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns"; import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp"; -import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { getMinExpiresIn } from "@app/lib/fn"; import { isDisposableEmail } from "@app/lib/validator"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; @@ -150,7 +150,8 @@ export const authSignupServiceFactory = ({ encryptedPrivateKeyTag, ip, userAgent, - authorization + authorization, + useDefaultOrg }: TCompleteAccountSignupDTO) => { const appCfg = getConfig(); const serverCfg = await getServerCfg(); @@ -293,15 +294,24 @@ export const authSignupServiceFactory = ({ }); if (!organizationId) { - const newOrganization = await orgService.createOrganization({ - userId: user.id, - userEmail: user.email ?? user.username, - orgName: organizationName - }); + let orgId = ""; + if (useDefaultOrg && serverCfg.defaultAuthOrgId) { + const defaultOrg = await orgDAL.findOrgById(serverCfg.defaultAuthOrgId); + if (!defaultOrg) throw new BadRequestError({ message: "Failed to find default organization" }); + orgId = defaultOrg.id; + } else { + if (!organizationName) throw new BadRequestError({ message: "Organization name is required" }); + const newOrganization = await orgService.createOrganization({ + userId: user.id, + userEmail: user.email ?? user.username, + orgName: organizationName + }); - if (!newOrganization) throw new Error("Failed to create organization"); + if (!newOrganization) throw new Error("Failed to create organization"); + orgId = newOrganization.id; + } - organizationId = newOrganization.id; + organizationId = orgId; } const updatedMembersips = await orgDAL.updateMembership( diff --git a/backend/src/services/auth/auth-signup-type.ts b/backend/src/services/auth/auth-signup-type.ts index 3308b9d12..8bbf302c5 100644 --- a/backend/src/services/auth/auth-signup-type.ts +++ b/backend/src/services/auth/auth-signup-type.ts @@ -12,12 +12,13 @@ export type TCompleteAccountSignupDTO = { encryptedPrivateKeyTag: string; salt: string; verifier: string; - organizationName: string; + organizationName?: string; providerAuthToken?: string | null; attributionSource?: string | undefined; ip: string; userAgent: string; authorization: string; + useDefaultOrg?: boolean; }; export type TCompleteAccountInviteDTO = { diff --git a/frontend/src/hooks/api/auth/types.ts b/frontend/src/hooks/api/auth/types.ts index 32610c28d..cbd20b643 100644 --- a/frontend/src/hooks/api/auth/types.ts +++ b/frontend/src/hooks/api/auth/types.ts @@ -107,6 +107,7 @@ export type CompleteAccountSignupDTO = CompleteAccountDTO & { providerAuthToken?: string; attributionSource?: string; organizationName: string; + useDefaultOrg?: boolean; }; export type VerifySignupInviteDTO = { diff --git a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx index a9242c252..af93b3c2a 100644 --- a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx @@ -235,7 +235,7 @@ export const OverviewPage = () => { Default organization
- Select the default organization you want to set for SAML/LDAP/OIDC based + Select the default organization you want to set for SAML/LDAP/OIDC/Github logins. When selected, user logins will be automatically scoped to the selected organization.
diff --git a/frontend/src/pages/auth/SignUpSsoPage/SignUpSsoPage.tsx b/frontend/src/pages/auth/SignUpSsoPage/SignUpSsoPage.tsx index 40075ae54..6cb4da82b 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/SignUpSsoPage.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/SignUpSsoPage.tsx @@ -13,6 +13,7 @@ export const SignupSsoPage = () => { const { t } = useTranslation(); const search = useSearch({ from: ROUTE_PATHS.Auth.SignUpSsoPage.id }); const token = search.token as string; + const defaultOrgAllowed = search.defaultOrgAllowed as boolean | undefined; const [step, setStep] = useState(0); const [password, setPassword] = useState(""); @@ -57,6 +58,7 @@ export const SignupSsoPage = () => { password={password} setPassword={setPassword} providerAuthToken={token} + forceDefaultOrg={defaultOrgAllowed} /> ); default: diff --git a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx index a6ab0623b..2f2ade8e7 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -30,6 +30,7 @@ type Props = { name: string; providerOrganizationName: string; providerAuthToken?: string; + forceDefaultOrg?: boolean; }; /** @@ -51,7 +52,8 @@ export const UserInfoSSOStep = ({ providerOrganizationName, password, setPassword, - providerAuthToken + providerAuthToken, + forceDefaultOrg }: Props) => { const [nameError, setNameError] = useState(false); const [organizationName, setOrganizationName] = useState(""); @@ -84,7 +86,7 @@ export const UserInfoSSOStep = ({ } else { setNameError(false); } - if (!organizationName) { + if (!organizationName && !forceDefaultOrg) { setOrganizationNameError(true); errorCheck = true; } else { @@ -160,7 +162,8 @@ export const UserInfoSSOStep = ({ salt: result.salt, verifier: result.verifier, organizationName, - attributionSource + attributionSource, + useDefaultOrg: forceDefaultOrg }); // unset signup JWT token and set JWT token @@ -267,7 +270,7 @@ export const UserInfoSSOStep = ({

)} - {providerOrganizationName === undefined && ( + {!forceDefaultOrg && providerOrganizationName === undefined && (

Organization Name @@ -279,7 +282,7 @@ export const UserInfoSSOStep = ({ isRequired className="h-12" maxLength={64} - disabled + isDisabled={forceDefaultOrg} /> {organizationNameError && (

diff --git a/frontend/src/pages/auth/SignUpSsoPage/route.tsx b/frontend/src/pages/auth/SignUpSsoPage/route.tsx index a255efaf6..986735ad0 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/route.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/route.tsx @@ -5,7 +5,8 @@ import { z } from "zod"; import { SignupSsoPage } from "./SignUpSsoPage"; const SignupSSOPageQueryParamsSchema = z.object({ - token: z.string() + token: z.string(), + defaultOrgAllowed: z.boolean().optional() }); export const Route = createFileRoute("/_restrict-login-signup/signup/sso")({ From 365b4b975e331e2dddcfffac67b3de94efde347e Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 2 May 2025 08:22:47 -0300 Subject: [PATCH 2/4] Add minor improvements to Github SSO users added to default organization on signup --- backend/src/server/lib/schemas.ts | 3 +-- backend/src/services/auth/auth-login-service.ts | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/src/server/lib/schemas.ts b/backend/src/server/lib/schemas.ts index 72ee8eb4f..7edc98db3 100644 --- a/backend/src/server/lib/schemas.ts +++ b/backend/src/server/lib/schemas.ts @@ -50,8 +50,7 @@ export const SecretNameSchema = BaseSecretNameSchema.refine( .refine((el) => !el.includes("/"), "Secret name cannot contain forward slash."); const DefaultOrgSchema = z.object({ - useDefaultOrg: z.literal(true), - organizationName: z.string().trim().optional() + useDefaultOrg: z.literal(true) }); const CustomOrgSchema = z.object({ diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 6e45ba7e1..14663ced6 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -727,7 +727,11 @@ export const authLoginServiceFactory = ({ if (authMethod === AuthMethod.GITHUB && serverCfg.defaultAuthOrgId) { let orgId = ""; const defaultOrg = await orgDAL.findOrgById(serverCfg.defaultAuthOrgId); - if (!defaultOrg) throw new BadRequestError({ message: "Failed to find default organization" }); + if (!defaultOrg) { + throw new BadRequestError({ + message: `Failed to find default organization with ID ${serverCfg.defaultAuthOrgId}` + }); + } orgId = defaultOrg.id; const [orgMembership] = await orgDAL.findMembership({ [`${TableName.OrgMembership}.userId` as "userId"]: user.id, From e12f4ad25339d6aae2c6fb8153110fdc7df72c2b Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 2 May 2025 12:58:36 -0300 Subject: [PATCH 3/4] Add cloud check on github add user to default org --- backend/src/server/routes/v1/sso-router.ts | 2 +- backend/src/services/auth/auth-login-service.ts | 2 +- backend/src/services/auth/auth-signup-service.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index 5f66a67ca..b6b3cb8aa 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -347,7 +347,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { const serverCfg = await getServerCfg(); return res.redirect( `${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${ - serverCfg.defaultAuthOrgId ? `&defaultOrgAllowed=true` : "" + serverCfg.defaultAuthOrgId && !appCfg.isCloud ? `&defaultOrgAllowed=true` : "" }` ); } diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 14663ced6..14aa8f038 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -724,7 +724,7 @@ export const authLoginServiceFactory = ({ isGhost: false }); - if (authMethod === AuthMethod.GITHUB && serverCfg.defaultAuthOrgId) { + if (authMethod === AuthMethod.GITHUB && serverCfg.defaultAuthOrgId && !appCfg.isCloud) { let orgId = ""; const defaultOrg = await orgDAL.findOrgById(serverCfg.defaultAuthOrgId); if (!defaultOrg) { diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index f3205bc3a..4d8c98205 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -295,7 +295,7 @@ export const authSignupServiceFactory = ({ if (!organizationId) { let orgId = ""; - if (useDefaultOrg && serverCfg.defaultAuthOrgId) { + if (useDefaultOrg && serverCfg.defaultAuthOrgId && !appCfg.isCloud) { const defaultOrg = await orgDAL.findOrgById(serverCfg.defaultAuthOrgId); if (!defaultOrg) throw new BadRequestError({ message: "Failed to find default organization" }); orgId = defaultOrg.id; From 46755f724cc57e8b725b0fad8faab9ab40f1330b Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 2 May 2025 13:06:45 -0300 Subject: [PATCH 4/4] Improve /complete-account/signup body schema --- backend/src/server/lib/schemas.ts | 19 ------------------ backend/src/server/routes/v3/signup-router.ts | 20 +++++++++++++++++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/backend/src/server/lib/schemas.ts b/backend/src/server/lib/schemas.ts index 7edc98db3..9f93eaea0 100644 --- a/backend/src/server/lib/schemas.ts +++ b/backend/src/server/lib/schemas.ts @@ -48,22 +48,3 @@ export const SecretNameSchema = BaseSecretNameSchema.refine( ) .refine((el) => !el.includes(":"), "Secret name cannot contain colon.") .refine((el) => !el.includes("/"), "Secret name cannot contain forward slash."); - -const DefaultOrgSchema = z.object({ - useDefaultOrg: z.literal(true) -}); - -const CustomOrgSchema = z.object({ - useDefaultOrg: z.literal(false), - organizationName: GenericResourceNameSchema -}); - -export const OrganizationInputSchema = z.preprocess( - (data) => { - if (typeof data === "object" && data && "useDefaultOrg" in data === false) { - return { ...data, useDefaultOrg: false }; - } - return data; - }, - z.discriminatedUnion("useDefaultOrg", [DefaultOrgSchema, CustomOrgSchema]) -); diff --git a/backend/src/server/routes/v3/signup-router.ts b/backend/src/server/routes/v3/signup-router.ts index 8c786355d..552253cde 100644 --- a/backend/src/server/routes/v3/signup-router.ts +++ b/backend/src/server/routes/v3/signup-router.ts @@ -4,7 +4,7 @@ import { UsersSchema } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { ForbiddenRequestError } from "@app/lib/errors"; import { authRateLimit } from "@app/server/config/rateLimiter"; -import { OrganizationInputSchema } from "@app/server/lib/schemas"; +import { GenericResourceNameSchema } from "@app/server/lib/schemas"; import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; @@ -106,7 +106,23 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => { attributionSource: z.string().trim().optional(), password: z.string() }) - .and(OrganizationInputSchema), + .and( + z.preprocess( + (data) => { + if (typeof data === "object" && data && "useDefaultOrg" in data === false) { + return { ...data, useDefaultOrg: false }; + } + return data; + }, + z.discriminatedUnion("useDefaultOrg", [ + z.object({ useDefaultOrg: z.literal(true) }), + z.object({ + useDefaultOrg: z.literal(false), + organizationName: GenericResourceNameSchema + }) + ]) + ) + ), response: { 200: z.object({ message: z.string(),