Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

Comment on lines +17 to +21
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.

useCheckAccessAndRedirect relies on checkAccess(permission), but checkAccess currently always returns true, so the redirect and hasAccess gating will never trigger. If this PR is meant to enforce permissions, checkAccess needs to implement real permission evaluation (e.g., based on the user/project role/permissions) or the new hook won’t have any effect.

Copilot uses AI. Check for mistakes.
useEffect(() => {
if (!hasAccess) {
navigate('/', { replace: true });
}
}, [hasAccess, navigate]);

return hasAccess;
};
Comment on lines +17 to +29
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.

The PR description still contains placeholders (e.g., Fixes #<issue_number>) and the template checklist isn’t filled out. Please update the PR metadata so reviewers can verify intent, scope, and testing expectations.

Copilot uses AI. Check for mistakes.
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ export const useMenuLinks = () => {
to: '/tables',
label: t('Tables'),
icon: TableProperties,
locked: !checkAccess(Permission.WRITE_TABLE),
},
...(hasAnalyticsAccess
? [
{
to: '/analytics',
label: t('Analytics'),
icon: LucideBarChart2,
locked: !checkAccess(Permission.WRITE_ANALYTICS),
},
]
: []),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAuthorization } from '@/app/common/hooks/authorization-hooks';
import { useCheckAccessAndRedirect } from '@/app/common/hooks/authorization-hooks';
import { useTheme } from '@/app/common/providers/theme-provider';
import { OPENOPS_CONNECT_TEMPLATES_URL } from '@/app/constants/cloud';
import { ExpandedTemplate } from '@/app/features/templates/components/expanded-template';
Expand Down Expand Up @@ -97,8 +97,9 @@ const SelectFlowTemplateDialogContent = ({
const ownerLogoUrl = useOwnerLogoUrl();
const { createPollingInterval } = useUserInfoPolling();
const { isCloudUser } = useShowTemplatesBanner();
const { checkAccess } = useAuthorization();
const hasWriteFlowPermission = checkAccess(Permission.WRITE_FLOW);
const hasWriteFlowPermission = useCheckAccessAndRedirect(
Permission.WRITE_FLOW,
);
Comment on lines +100 to +102
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.

This dialog content previously used checkAccess only to disable the “use template” action; switching to useCheckAccessAndRedirect will navigate the user away from the current page when they lack WRITE_FLOW, even though the UI already supports a read-only experience (it just omits useTemplate). Consider using useAuthorization().checkAccess here (no redirect), or make the redirect hook optional/configurable so non-route components don’t cause unexpected navigation.

Copilot uses AI. Check for mistakes.
const isFullCatalog = !isTemplatePreselected && isCloudUser;

const onExploreMoreClick = () => {
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;
}

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: 7 additions & 2 deletions packages/react-ui/src/app/routes/openops-analytics/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 } from '@openops/shared';
import { FlagId, Permission } from '@openops/shared';

import {
AnalyticsDashboardEmptyState,
Expand All @@ -14,7 +15,7 @@ import { useEmbedDashboard } from './use-embed-dashboard';

const OpenOpsAnalyticsPage = () => {
useDefaultSidebarState('minimized');

const hasAccess = useCheckAccessAndRedirect(Permission.WRITE_ANALYTICS);
const { isCanduEnabled, canduClientToken, canduUserId } = useCandu();
const { data: analyticsPublicUrl } = flagsHooks.useFlag<string | undefined>(
FlagId.ANALYTICS_PUBLIC_URL,
Expand All @@ -36,6 +37,10 @@ const OpenOpsAnalyticsPage = () => {
canduUserId,
});

if (!hasAccess) {
return null;
}

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

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 } from '@openops/shared';
import { FlagId, Permission } from '@openops/shared';

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

if (!hasAccess) {
return null;
}

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