Skip to content
Merged
17 changes: 16 additions & 1 deletion packages/react-ui/src/app/common/hooks/authorization-hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import { authenticationSession } from '@/app/lib/authentication-session';
import { Permission } from '@openops/shared';
Expand All @@ -12,3 +13,17 @@ export const useAuthorization = () => {

return { checkAccess, role };
};

export const useCheckAccessAndRedirect = (permission: Permission) => {
const { checkAccess } = useAuthorization();
const navigate = useNavigate();
const hasAccess = checkAccess(permission);

useEffect(() => {
if (!hasAccess) {
navigate('/', { replace: true });
}
}, [hasAccess, navigate]);

return hasAccess;
};
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ const HomeOnboardingView = ({
return (
<div className="flex flex-col gap-6 flex-1">
{isFinOpsBenchmarkEnabled && (
<PermissionGuard permission={Permission.WRITE_FLOW}>
<PermissionGuard
permission={[Permission.WRITE_FLOW, Permission.READ_APP_CONNECTION]}
>
<FinOpsBenchmarkBanner
variation={benchmarkVariation}
provider={benchmarkProvider}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ const HomeOperationalView = ({
)}

{isFinOpsBenchmarkEnabled && (
<PermissionGuard permission={Permission.WRITE_FLOW}>
<PermissionGuard
permission={[Permission.WRITE_FLOW, Permission.READ_APP_CONNECTION]}
>
<FinOpsBenchmarkBanner
variation={benchmarkVariation}
provider={benchmarkProvider}
Expand Down
8 changes: 7 additions & 1 deletion packages/react-ui/src/app/routes/flows/id/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PopulatedFlow } from '@openops/shared';
import { Permission, PopulatedFlow } from '@openops/shared';
import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
import { Navigate, useParams, useSearchParams } from 'react-router-dom';

import { FullPageSpinner } from '@/app/common/components/full-page-spinner';
import { useCheckAccessAndRedirect } from '@/app/common/hooks/authorization-hooks';
import { QueryKeys } from '@/app/constants/query-keys';
import { SEARCH_PARAMS } from '@/app/constants/search-params';
import { BuilderPage } from '@/app/features/builder';
Expand All @@ -15,6 +16,7 @@ import { flowsApi } from '@/app/features/flows/lib/flows-api';
import { AxiosError } from 'axios';

const FlowBuilderPage = () => {
const hasAccess = useCheckAccessAndRedirect(Permission.READ_FLOW);
const { flowId } = useParams();
const [searchParams, setSearchParams] = useSearchParams();

Expand Down Expand Up @@ -51,6 +53,10 @@ const FlowBuilderPage = () => {
refetchOnWindowFocus: 'always',
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useQuery will still fetch the flow even when hasAccess is false (the redirect happens in an effect after render). To avoid unnecessary API calls (and caching potentially sensitive data client-side), gate the query with an enabled condition that includes hasAccess (and flowId).

Suggested change
refetchOnWindowFocus: 'always',
refetchOnWindowFocus: 'always',
enabled: Boolean(hasAccess && flowId),

Copilot uses AI. Check for mistakes.
});

if (!hasAccess) {
return null;
}
Comment on lines +56 to +58
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need it? In theory it is not reachable as user will be redirected. But we can keep it to be more confident


if (isError && (error as AxiosError).status === 404) {
return <Navigate to="/404" />;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/react-ui/src/app/routes/flows/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import qs from 'qs';
import { useCallback, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

import { useCheckAccessAndRedirect } from '@/app/common/hooks/authorization-hooks';
import { useDefaultSidebarState } from '@/app/common/hooks/use-default-sidebar-state';
import { isModifierOrMiddleClick } from '@/app/common/navigation/table-navigation-helper';
import {
Expand All @@ -16,10 +17,11 @@ import {
import { flowsApi } from '@/app/features/flows/lib/flows-api';
import { FlowsFolderBreadcrumbsManager } from '@/app/features/folders/component/folder-breadcrumbs-manager';
import { FLOWS_TABLE_FILTERS } from '@/app/features/folders/lib/flows-table-filters';
import { FlowStatus, FlowVersionState } from '@openops/shared';
import { FlowStatus, FlowVersionState, Permission } from '@openops/shared';

const FlowsPage = () => {
useDefaultSidebarState('expanded');
const hasAccess = useCheckAccessAndRedirect(Permission.READ_FLOW);
const navigate = useNavigate();
const [tableRefresh, setTableRefresh] = useState(false);
const onTableRefresh = useCallback(
Expand Down Expand Up @@ -58,6 +60,10 @@ const FlowsPage = () => {
[],
);

if (!hasAccess) {
return null;
}

return (
<div className="flex flex-col w-full">
<div className="px-7">
Expand Down
9 changes: 2 additions & 7 deletions packages/react-ui/src/app/routes/openops-analytics/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Navigate } from 'react-router-dom';
import { useCheckAccessAndRedirect } from '@/app/common/hooks/authorization-hooks';

import { useAuthorization } from '@/app/common/hooks/authorization-hooks';
import { flagsHooks } from '@/app/common/hooks/flags-hooks';
import { useDefaultSidebarState } from '@/app/common/hooks/use-default-sidebar-state';
import { useCandu } from '@/app/features/extensions/candu/use-candu';
Expand All @@ -17,7 +16,7 @@ import { useEmbedDashboard } from './use-embed-dashboard';

const OpenOpsAnalyticsPage = () => {
useDefaultSidebarState('minimized');
const { checkAccess } = useAuthorization();
useCheckAccessAndRedirect(Permission.WRITE_ANALYTICS);
const { isCanduEnabled, canduClientToken, canduUserId } = useCandu();
const { data: analyticsPublicUrl } = flagsHooks.useFlag<string | undefined>(
FlagId.ANALYTICS_PUBLIC_URL,
Expand All @@ -39,10 +38,6 @@ const OpenOpsAnalyticsPage = () => {
canduUserId,
});

if (!checkAccess(Permission.WRITE_ANALYTICS)) {
return <Navigate to="/" replace />;
}

if (!analyticsPublicUrl) {
console.error('OpenOps Analytics URL is not defined');
return null;
Expand Down
10 changes: 3 additions & 7 deletions packages/react-ui/src/app/routes/openops-tables/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { t } from 'i18next';
import { Navigate, useLocation } from 'react-router-dom';
import { useLocation } from 'react-router-dom';

import { useAuthorization } from '@/app/common/hooks/authorization-hooks';
import { useCheckAccessAndRedirect } from '@/app/common/hooks/authorization-hooks';
import { flagsHooks } from '@/app/common/hooks/flags-hooks';
import { useDefaultSidebarState } from '@/app/common/hooks/use-default-sidebar-state';
import { useCandu } from '@/app/features/extensions/candu/use-candu';
import { FlagId, Permission } from '@openops/shared';

const OpenOpsTablesPage = () => {
useDefaultSidebarState('minimized');
const { checkAccess } = useAuthorization();
useCheckAccessAndRedirect(Permission.WRITE_TABLE);
const { isCanduEnabled, canduClientToken, canduUserId } = useCandu();
const parentData = encodeURIComponent(
JSON.stringify({ isCanduEnabled, userId: canduUserId, canduClientToken }),
Expand All @@ -24,10 +24,6 @@ const OpenOpsTablesPage = () => {
FlagId.OPENOPS_TABLES_PUBLIC_URL,
);

if (!checkAccess(Permission.WRITE_TABLE)) {
return <Navigate to="/" replace />;
}

if (!openopsTablesUrl) {
console.error('OpenOps Tables URL is not defined');
return null;
Expand Down
Loading