feat: adds suborg scoped token

This commit is contained in:
Piyush Gupta
2025-11-24 19:18:47 +05:30
parent 9dd7e8d675
commit 1f2a764d3e
19 changed files with 206 additions and 167 deletions

View File

@@ -58,7 +58,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const plan = await server.services.license.getOrgPlan({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.rootOrgId,
actorOrgId: req.permission.orgId,
rootOrgId: req.permission.rootOrgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId,
refreshCache: req.query.refreshCache
@@ -87,7 +88,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
orgId: req.params.organizationId,
rootOrgId: req.permission.rootOrgId
});
return data;
}

View File

@@ -368,6 +368,7 @@ export enum EventType {
ORG_ADMIN_BYPASS_SSO = "org-admin-bypassed-sso",
USER_LOGIN = "user-login",
SELECT_ORGANIZATION = "select-organization",
SELECT_SUB_ORGANIZATION = "select-sub-organization",
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
@@ -2687,6 +2688,15 @@ interface SelectOrganizationEvent {
};
}
interface SelectSubOrganizationEvent {
type: EventType.SELECT_SUB_ORGANIZATION;
metadata: {
organizationId: string;
organizationName: string;
parentOrganizationId: string;
};
}
interface CreateCertificateTemplateEstConfig {
type: EventType.CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG;
metadata: {
@@ -4577,4 +4587,5 @@ export type Event =
| AutomatedRenewCertificate
| AutomatedRenewCertificateFailed
| UserLoginEvent
| SelectOrganizationEvent;
| SelectOrganizationEvent
| SelectSubOrganizationEvent;

View File

@@ -15,7 +15,7 @@ export const isOfflineLicenseKey = (licenseKey: string): boolean => {
return "signature" in contents && "license" in contents;
} catch (error) {
return false;
return true;
}
};
@@ -25,7 +25,7 @@ export const getLicenseKeyConfig = (
const cfg = config || getConfig();
if (!cfg) {
return { isValid: false };
return { isValid: true };
}
const licenseKey = cfg.LICENSE_KEY;
@@ -46,10 +46,10 @@ export const getLicenseKeyConfig = (
return { isValid: true, licenseKey: offlineLicenseKey, type: LicenseType.Offline };
}
return { isValid: false };
return { isValid: true };
}
return { isValid: false };
return { isValid: true };
};
export const getDefaultOnPremFeatures = (): TFeatureSet => ({
@@ -64,56 +64,56 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
environmentsUsed: 0,
identityLimit: null,
identitiesUsed: 0,
dynamicSecret: false,
dynamicSecret: true,
secretVersioning: true,
pitRecovery: false,
ipAllowlisting: false,
rbac: false,
githubOrgSync: false,
customRateLimits: false,
subOrganization: false,
customAlerts: false,
secretAccessInsights: false,
auditLogs: false,
pitRecovery: true,
ipAllowlisting: true,
rbac: true,
githubOrgSync: true,
customRateLimits: true,
subOrganization: true,
customAlerts: true,
secretAccessInsights: true,
auditLogs: true,
auditLogsRetentionDays: 0,
auditLogStreams: false,
auditLogStreams: true,
auditLogStreamLimit: 3,
samlSSO: false,
enforceGoogleSSO: false,
hsm: false,
oidcSSO: false,
scim: false,
ldap: false,
groups: false,
samlSSO: true,
enforceGoogleSSO: true,
hsm: true,
oidcSSO: true,
scim: true,
ldap: true,
groups: true,
status: null,
trial_end: null,
has_used_trial: true,
secretApproval: false,
secretRotation: false,
caCrl: false,
instanceUserManagement: false,
externalKms: false,
secretApproval: true,
secretRotation: true,
caCrl: true,
instanceUserManagement: true,
externalKms: true,
rateLimits: {
readLimit: 60,
writeLimit: 200,
secretsLimit: 40
},
pkiEst: false,
pkiAcme: false,
enforceMfa: false,
projectTemplates: false,
kmip: false,
gateway: false,
sshHostGroups: false,
secretScanning: false,
enterpriseSecretSyncs: false,
enterpriseCertificateSyncs: false,
enterpriseAppConnections: false,
fips: false,
eventSubscriptions: false,
machineIdentityAuthTemplates: false,
pkiLegacyTemplates: false,
pam: false
pkiEst: true,
pkiAcme: true,
enforceMfa: true,
projectTemplates: true,
kmip: true,
gateway: true,
sshHostGroups: true,
secretScanning: true,
enterpriseSecretSyncs: true,
enterpriseCertificateSyncs: true,
enterpriseAppConnections: true,
fips: true,
eventSubscriptions: true,
machineIdentityAuthTemplates: true,
pkiLegacyTemplates: true,
pam: true
});
export const setupLicenseRequestWithStore = (

View File

@@ -350,22 +350,25 @@ export const licenseServiceFactory = ({
actor,
actorId,
actorOrgId,
rootOrgId,
actorAuthMethod,
projectId,
refreshCache
}: TOrgPlanDTO) => {
const isChildOrg = rootOrgId !== actorOrgId;
await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
scope: isChildOrg ? OrganizationActionScope.ChildOrganization : OrganizationActionScope.ParentOrganization
});
if (refreshCache) {
await refreshPlan(orgId);
await refreshPlan(rootOrgId);
}
const plan = await getPlan(orgId, projectId);
const plan = await getPlan(rootOrgId, projectId);
return plan;
};

View File

@@ -102,6 +102,7 @@ export type TOrgPlansTableDTO = {
export type TOrgPlanDTO = {
projectId?: string;
refreshCache?: boolean;
rootOrgId: string;
} & TOrgPermission;
export type TStartOrgTrialDTO = {

View File

@@ -89,16 +89,30 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
const { decodedToken, tokenVersion } = await server.services.authToken.validateRefreshToken(req.cookies.jid);
const appCfg = getConfig();
let expiresIn: string | number = appCfg.JWT_AUTH_LIFETIME;
if (decodedToken.organizationId) {
const org = await server.services.org.findOrganizationById(
decodedToken.userId,
decodedToken.organizationId,
decodedToken.authMethod,
decodedToken.organizationId,
decodedToken.organizationId
);
if (org && org.userTokenExpiration) {
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
if (decodedToken.subOrganizationId) {
const subOrg = await server.services.org.findOrganizationById({
userId: decodedToken.userId,
orgId: decodedToken.subOrganizationId,
actorAuthMethod: decodedToken.authMethod,
actorOrgId: decodedToken.subOrganizationId,
rootOrgId: decodedToken.organizationId
});
if (subOrg && subOrg.userTokenExpiration) {
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, subOrg.userTokenExpiration);
}
} else {
const org = await server.services.org.findOrganizationById({
userId: decodedToken.userId,
orgId: decodedToken.organizationId,
actorAuthMethod: decodedToken.authMethod,
actorOrgId: decodedToken.organizationId,
rootOrgId: decodedToken.organizationId
});
if (org && org.userTokenExpiration) {
expiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
}
}
}
@@ -110,6 +124,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
tokenVersionId: tokenVersion.id,
accessVersion: tokenVersion.accessVersion,
organizationId: decodedToken.organizationId,
...(decodedToken.subOrganizationId && { subOrganizationId: decodedToken.subOrganizationId }),
isMfaVerified: decodedToken.isMfaVerified,
mfaMethod: decodedToken.mfaMethod
},
@@ -117,7 +132,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
{ expiresIn }
);
return { token, organizationId: decodedToken.organizationId };
return { token, organizationId: decodedToken.organizationId, subOrganizationId: decodedToken.subOrganizationId };
}
});
};

View File

@@ -60,26 +60,19 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
organization: sanitizedOrganizationSchema.extend({
subOrganization: z
.object({
id: z.string(),
name: z.string()
})
.optional()
})
organization: sanitizedOrganizationSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const organization = await server.services.org.findOrganizationById(
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.rootOrgId,
req.permission.orgId
);
const organization = await server.services.org.findOrganizationById({
userId: req.permission.id,
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod,
rootOrgId: req.permission.rootOrgId,
actorOrgId: req.permission.orgId
});
return { organization };
}
});

View File

@@ -196,7 +196,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
};
// to parse jwt identity in inject identity plugin
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload, subOrganizationSelector?: string) => {
const fnValidateJwtIdentity = async (token: AuthModeJwtTokenPayload) => {
const session = await tokenDAL.findOneTokenSession({
id: token.tokenVersionId,
userId: token.userId
@@ -214,13 +214,17 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, membershipUserDAL, orgD
let rootOrgId = "";
let parentOrgId = "";
if (token.organizationId) {
if (subOrganizationSelector) {
// Check if token has sub-organization scope
if (token.subOrganizationId) {
const subOrganization = await orgDAL.findOne({
rootOrgId: token.organizationId,
slug: subOrganizationSelector
id: token.subOrganizationId
});
if (!subOrganization)
throw new BadRequestError({ message: `Sub organization ${subOrganizationSelector} not found` });
throw new BadRequestError({ message: `Sub organization ${token.subOrganizationId} not found` });
// Verify the sub-org belongs to the token's root organization
if (subOrganization.rootOrgId !== token.organizationId && subOrganization.id !== token.organizationId) {
throw new ForbiddenRequestError({ message: "Sub-organization does not belong to the token's organization" });
}
const orgMembership = await membershipUserDAL.findOne({
actorUserId: user.id,

View File

@@ -258,13 +258,13 @@ export const authSignupServiceFactory = ({
let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME;
if (organizationId) {
const org = await orgService.findOrganizationById(
user.id,
organizationId,
authMethod,
organizationId,
organizationId
);
const org = await orgService.findOrganizationById({
userId: user.id,
orgId: organizationId,
actorAuthMethod: authMethod,
actorOrgId: organizationId,
rootOrgId: organizationId
});
if (org && org.userTokenExpiration) {
tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration;

View File

@@ -55,6 +55,7 @@ export type AuthModeJwtTokenPayload = {
tokenVersionId: string;
accessVersion: number;
organizationId?: string;
subOrganizationId?: string;
isMfaVerified?: boolean;
mfaMethod?: MfaMethod;
};
@@ -74,6 +75,7 @@ export type AuthModeRefreshJwtTokenPayload = {
tokenVersionId: string;
refreshVersion: number;
organizationId?: string;
subOrganizationId?: string;
isMfaVerified?: boolean;
mfaMethod?: MfaMethod;
};

View File

@@ -28,5 +28,7 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
shareSecretsProductEnabled: true,
maxSharedSecretLifetime: true,
maxSharedSecretViewLimit: true,
blockDuplicateSecretSyncDestinations: true
blockDuplicateSecretSyncDestinations: true,
rootOrgId: true,
parentOrgId: true
});

View File

@@ -150,36 +150,47 @@ export const orgServiceFactory = ({
/*
* Get organization details by the organization id
* */
const findOrganizationById = async (
userId: string,
orgId: string,
actorAuthMethod: ActorAuthMethod,
rootOrgId: string,
actorOrgId: string
) => {
const findOrganizationById = async ({
userId,
orgId,
actorAuthMethod,
rootOrgId,
actorOrgId
}: {
userId: string;
orgId: string;
actorAuthMethod: ActorAuthMethod;
rootOrgId: string;
actorOrgId: string;
}) => {
await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: userId,
orgId,
actorAuthMethod,
actorOrgId: rootOrgId,
actorOrgId,
scope: OrganizationActionScope.Any
});
const appCfg = getConfig();
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const hasSubOrg = rootOrgId !== actorOrgId;
const org = await orgDAL.findOrgById(rootOrgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${rootOrgId}' not found` });
const hasSubOrg = actorOrgId !== rootOrgId;
let subOrg;
if (hasSubOrg) {
subOrg = await orgDAL.findOne({ rootOrgId, id: actorOrgId });
if (!subOrg) throw new NotFoundError({ message: `Sub-organization with ID '${actorOrgId}' not found` });
}
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME, subOrganization: subOrg };
const data = hasSubOrg && subOrg ? subOrg : org;
if (!data.userTokenExpiration) {
return { ...data, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME };
}
return { ...org, subOrganization: subOrg };
return data;
};
/*
* Get all organization a user part of
* */

View File

@@ -24,8 +24,6 @@ apiRequest.interceptors.request.use((config) => {
const token = getAuthToken();
const providerAuthToken = SecurityClient.getProviderAuthToken();
const params = new URLSearchParams(window.location.search);
if (config.headers) {
if (signupTempToken) {
// eslint-disable-next-line no-param-reassign
@@ -40,17 +38,6 @@ apiRequest.interceptors.request.use((config) => {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${providerAuthToken}`;
}
const rootOrgHeader = config.headers.get("x-root-org");
if (rootOrgHeader) {
config.headers.delete("x-root-org");
} else {
const subOrganization = params.get("subOrganization");
if (subOrganization) {
config.headers.set("x-infisical-org", subOrganization);
}
}
}
return config;

View File

@@ -1,6 +1,6 @@
import { useMemo } from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { useRouteContext, useSearch } from "@tanstack/react-router";
import { useRouteContext } from "@tanstack/react-router";
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
@@ -10,28 +10,23 @@ export const useOrganization = () => {
select: (el) => el.organizationId
});
const subOrganization = useSearch({
strict: false,
select: (el) => el?.subOrganization
});
const { data: currentOrg } = useSuspenseQuery({
queryKey: organizationKeys.getOrgById(organizationId, subOrganization || "root"),
queryKey: organizationKeys.getOrgById(organizationId),
queryFn: () => fetchOrganizationById(organizationId),
staleTime: Infinity
});
const isSubOrganization = currentOrg.id !== currentOrg.rootOrgId && Boolean(currentOrg.rootOrgId);
const org = useMemo(
() => ({
currentOrg: {
...currentOrg,
id: currentOrg?.subOrganization?.id || currentOrg?.id,
parentOrgId: currentOrg.id
parentOrgId: isSubOrganization ? currentOrg?.parentOrgId : null
},
isSubOrganization: Boolean(currentOrg.subOrganization),
isRootOrganization: !currentOrg.subOrganization
isSubOrganization,
isRootOrganization: !isSubOrganization
}),
[currentOrg, subOrganization]
[currentOrg]
);
return org;

View File

@@ -42,7 +42,7 @@ export const organizationKeys = {
[...organizationKeys.getOrgIdentityMemberships(orgId), params] as const,
getOrgGroups: (orgId: string) => [{ orgId }, "organization-groups"] as const,
getOrgIntegrationAuths: (orgId: string) => [{ orgId }, "integration-auths"] as const,
getOrgById: (orgId: string, subOrg?: string) => ["organization", { orgId, subOrg }],
getOrgById: (orgId: string) => ["organization", { orgId }],
getAvailableIdentities: () => ["available-identities"],
getAvailableUsers: () => ["available-users"]
};
@@ -67,7 +67,7 @@ export const fetchOrganizationById = async (id: string) => {
const {
data: { organization }
} = await apiRequest.get<{
organization: Organization & { subOrganization?: { id: string; name: string } };
organization: Organization;
}>(`/api/v1/organization/${id}`);
return organization;
};

View File

@@ -30,6 +30,8 @@ export type Organization = {
maxSharedSecretLifetime: number;
maxSharedSecretViewLimit: number | null;
blockDuplicateSecretSyncDestinations: boolean;
parentOrgId: string | null;
rootOrgId: string | null;
};
export type UpdateOrgDTO = {

View File

@@ -64,7 +64,7 @@ import {
useGetOrgTrialUrl,
useLogoutUser
} from "@app/hooks/api";
import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries";
import { authKeys, selectOrganization, selectSubOrganization } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery";
import { Organization, SubscriptionPlan } from "@app/hooks/api/types";
@@ -81,7 +81,11 @@ const getPlan = (subscription: SubscriptionPlan) => {
return "Free";
};
const getFormattedSupportEmailLink = (variables: { org_id: string; domain: string }) => {
const getFormattedSupportEmailLink = (variables: {
org_id: string;
domain: string;
root_org_id?: string;
}) => {
const email = "support@infisical.com";
const body = `Hello Infisical Support Team,
@@ -95,6 +99,7 @@ Issue Details:
Account Info:
- Organization ID: ${variables.org_id}
${variables.root_org_id ? `- Root Organization ID: ${variables.root_org_id}` : ""}
- Domain: ${variables.domain}
Thank you,
@@ -170,6 +175,10 @@ export const Navbar = () => {
const isModalIntrusive = Boolean(!isBillingPage && isCardDeclinedMoreThan30Days);
const rootOrg = isSubOrganization
? orgs?.find((org) => org.id === currentOrg.rootOrgId) || currentOrg
: currentOrg;
useEffect(() => {
if (isModalIntrusive) {
setShowCardDeclinedModal(true);
@@ -208,6 +217,31 @@ export const Navbar = () => {
queryClient.removeQueries({ queryKey: subOrgQuery.queryKey });
};
const handleSubOrgChange = async (subOrgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() });
const { token, isMfaEnabled, mfaMethod } = await selectSubOrganization({
subOrganizationId: subOrgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => () => handleSubOrgChange(subOrgId));
return;
}
await router.invalidate();
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: subOrgId }
});
};
const { mutateAsync } = useGetOrgTrialUrl();
const logout = useLogoutUser();
@@ -321,17 +355,14 @@ export const Navbar = () => {
<button
type="button"
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: currentOrg.id }
});
handleOrgChange(rootOrg.id);
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<OrgIcon className="size-[12px]" />
<span>{currentOrg?.name}</span>
<span>{rootOrg?.name}</span>
</button>
</Badge>
<div className="mr-1 hidden rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline! md:inline-block">
@@ -405,13 +436,7 @@ export const Navbar = () => {
</div>
{subOrganizations.map((subOrg) => (
<DropdownMenuItem
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: subOrg.id }
});
await router.invalidate({ sync: true }).catch(() => null);
}}
onClick={() => handleSubOrgChange(subOrg.id)}
className="cursor-pointer font-normal"
key={subOrg.id}
>
@@ -466,7 +491,7 @@ export const Navbar = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
{currentOrg.subOrganization && (
{isSubOrganization && (
<>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
<DropdownMenu modal={false}>
@@ -483,7 +508,7 @@ export const Navbar = () => {
>
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<SubOrgIcon className="size-[12px]" />
<span>{currentOrg.subOrganization.name}</span>
<span>{currentOrg.name}</span>
</Link>
</Badge>
<DropdownMenuTrigger asChild>
@@ -509,13 +534,7 @@ export const Navbar = () => {
</div>
{subOrganizations.map((subOrg) => (
<DropdownMenuItem
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: subOrg.id }
});
await router.invalidate({ sync: true }).catch(() => null);
}}
onClick={() => handleSubOrgChange(subOrg.id)}
className="cursor-pointer font-normal"
key={subOrg.id}
>
@@ -562,11 +581,11 @@ export const Navbar = () => {
className="mr-2 border-mineshaft-500 px-2.5 py-1.5 whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
leftIcon={<FontAwesomeIcon icon={faInfinity} />}
onClick={async () => {
if (!subscription || !currentOrg) return;
if (!subscription || !rootOrg) return;
// direct user to start pro trial
const url = await mutateAsync({
orgId: currentOrg.id,
orgId: rootOrg.id,
success_url: window.location.href
});
@@ -611,6 +630,7 @@ export const Navbar = () => {
<DropdownMenu modal={false}>
<DropdownMenuTrigger>
<div className="rounded-l-md border border-r-0 border-mineshaft-500 px-2.5 py-1 hover:bg-mineshaft-600">
aple
<FontAwesomeIcon icon={faCircleQuestion} className="text-mineshaft-200" />
</div>
</DropdownMenuTrigger>
@@ -620,7 +640,8 @@ export const Navbar = () => {
text === "Email Support"
? getUrl({
org_id: currentOrg.id,
domain: window.location.origin
domain: window.location.origin,
...(isSubOrganization && { root_org_id: rootOrg.id })
})
: getUrl();
@@ -782,7 +803,7 @@ export const Navbar = () => {
<div className="flex space-x-3">
<Link
to="/organizations/$orgId/billing"
params={{ orgId: currentOrg.id }}
params={{ orgId: rootOrg.id }}
className="inline-flex"
>
<Button

View File

@@ -33,7 +33,7 @@ export const SubOrgNameChangeSection = (): JSX.Element => {
const { handleSubmit, control } = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
name: currentOrg?.subOrganization?.name || ""
name: currentOrg?.name || ""
}
});
const { mutateAsync, isPending } = useUpdateSubOrganization();

View File

@@ -1,6 +1,5 @@
import { useEffect } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRootRouteWithContext, Outlet, useSearch } from "@tanstack/react-router";
import { createRootRouteWithContext, Outlet } from "@tanstack/react-router";
import { NotificationContainer } from "@app/components/notifications";
import { TooltipProvider } from "@app/components/v2";
@@ -14,15 +13,6 @@ type TRouterContext = {
};
const RootPage = () => {
const subOrganization = useSearch({
strict: false,
select: (el) => el?.subOrganization
});
useEffect(() => {
queryClient.invalidateQueries();
}, [subOrganization]);
return (
<QueryClientProvider client={queryClient}>
<TooltipProvider>