diff --git a/backend/src/ee/routes/v1/license-router.ts b/backend/src/ee/routes/v1/license-router.ts index 2ccdce93a..c3c48bdc9 100644 --- a/backend/src/ee/routes/v1/license-router.ts +++ b/backend/src/ee/routes/v1/license-router.ts @@ -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; } diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index ab5b126c6..21ea764d9 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -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; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 09ff9e108..d808c0181 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -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 = ( diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index e34f9273f..2113b4c00 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -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; }; diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 8897eaabc..75bf4b067 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -102,6 +102,7 @@ export type TOrgPlansTableDTO = { export type TOrgPlanDTO = { projectId?: string; refreshCache?: boolean; + rootOrgId: string; } & TOrgPermission; export type TStartOrgTrialDTO = { diff --git a/backend/src/server/routes/v1/auth-router.ts b/backend/src/server/routes/v1/auth-router.ts index 48939844e..912bee642 100644 --- a/backend/src/server/routes/v1/auth-router.ts +++ b/backend/src/server/routes/v1/auth-router.ts @@ -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 }; } }); }; diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index d4b0058f9..a56fdcfed 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -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 }; } }); diff --git a/backend/src/services/auth-token/auth-token-service.ts b/backend/src/services/auth-token/auth-token-service.ts index fb7213109..c090f02de 100644 --- a/backend/src/services/auth-token/auth-token-service.ts +++ b/backend/src/services/auth-token/auth-token-service.ts @@ -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, diff --git a/backend/src/services/auth/auth-signup-service.ts b/backend/src/services/auth/auth-signup-service.ts index 14f4387b9..ae27b113d 100644 --- a/backend/src/services/auth/auth-signup-service.ts +++ b/backend/src/services/auth/auth-signup-service.ts @@ -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; diff --git a/backend/src/services/auth/auth-type.ts b/backend/src/services/auth/auth-type.ts index ef54ac0be..9e692dcd3 100644 --- a/backend/src/services/auth/auth-type.ts +++ b/backend/src/services/auth/auth-type.ts @@ -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; }; diff --git a/backend/src/services/org/org-schema.ts b/backend/src/services/org/org-schema.ts index be5c300b5..dbdd04586 100644 --- a/backend/src/services/org/org-schema.ts +++ b/backend/src/services/org/org-schema.ts @@ -28,5 +28,7 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({ shareSecretsProductEnabled: true, maxSharedSecretLifetime: true, maxSharedSecretViewLimit: true, - blockDuplicateSecretSyncDestinations: true + blockDuplicateSecretSyncDestinations: true, + rootOrgId: true, + parentOrgId: true }); diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index b0bbd9a2a..abcdebf51 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -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 * */ diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 16b8b4f45..a37b38cb6 100644 --- a/frontend/src/config/request.ts +++ b/frontend/src/config/request.ts @@ -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; diff --git a/frontend/src/context/OrganizationContext/OrganizationContext.tsx b/frontend/src/context/OrganizationContext/OrganizationContext.tsx index bfc17d143..ef5edcc0f 100644 --- a/frontend/src/context/OrganizationContext/OrganizationContext.tsx +++ b/frontend/src/context/OrganizationContext/OrganizationContext.tsx @@ -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; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index 9e2413b28..857358ffe 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -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; }; diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index b366277f1..45870176f 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -30,6 +30,8 @@ export type Organization = { maxSharedSecretLifetime: number; maxSharedSecretViewLimit: number | null; blockDuplicateSecretSyncDestinations: boolean; + parentOrgId: string | null; + rootOrgId: string | null; }; export type UpdateOrgDTO = { diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx index a3875ba49..d0f38b089 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/Navbar.tsx @@ -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 = () => {
/