diff --git a/ui/changelog.d/provider-alias-subscription-gate.fixed.md b/ui/changelog.d/provider-alias-subscription-gate.fixed.md
new file mode 100644
index 0000000000..e88e5710b1
--- /dev/null
+++ b/ui/changelog.d/provider-alias-subscription-gate.fixed.md
@@ -0,0 +1 @@
+`Edit Provider Alias` is now disabled with a subscription badge for Prowler Cloud accounts without an active subscription, instead of failing on submit
diff --git a/ui/components/providers/table/data-table-row-actions.test.tsx b/ui/components/providers/table/data-table-row-actions.test.tsx
index 0a0a774b50..17a10fa8f4 100644
--- a/ui/components/providers/table/data-table-row-actions.test.tsx
+++ b/ui/components/providers/table/data-table-row-actions.test.tsx
@@ -468,6 +468,92 @@ describe("DataTableRowActions", () => {
expect(screen.queryByText("Edit Scan Schedule")).not.toBeInTheDocument();
});
+ it("disables Edit Provider Alias with a subscription badge for manual-only Cloud provider rows", async () => {
+ // Given a Prowler Cloud account without a subscription (trial/onboarding):
+ // editing the alias would be rejected by the API, so the UI must not offer it.
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("button"));
+
+ // Then the action is still discoverable but disabled with an upsell badge.
+ expect(screen.getByText("Requires subscription")).toBeInTheDocument();
+ expect(
+ screen.getByRole("menuitem", { name: /edit provider alias/i }),
+ ).toHaveAttribute("aria-disabled", "true");
+ });
+
+ it("disables Edit Provider Alias for blocked Cloud provider rows", async () => {
+ // Given
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("button"));
+
+ // Then
+ expect(screen.getByText("Requires subscription")).toBeInTheDocument();
+ expect(
+ screen.getByRole("menuitem", { name: /edit provider alias/i }),
+ ).toHaveAttribute("aria-disabled", "true");
+ });
+
+ it("opens Edit Provider Alias for subscribed Cloud provider rows", async () => {
+ // Given a subscribed Cloud account (ADVANCED): the alias edit works.
+ vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ // When
+ await user.click(screen.getByRole("button"));
+
+ // Then the item is enabled (no upsell badge) and opens the alias modal.
+ expect(screen.queryByText("Requires subscription")).not.toBeInTheDocument();
+ await user.click(screen.getByText("Edit Provider Alias"));
+ expect(
+ screen.getByRole("dialog", { name: /edit provider alias/i }),
+ ).toBeInTheDocument();
+ });
+
it("opens scan config management with the precomputed current config id", async () => {
// Given
vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
diff --git a/ui/components/providers/table/data-table-row-actions.tsx b/ui/components/providers/table/data-table-row-actions.tsx
index c43631f754..788012c950 100644
--- a/ui/components/providers/table/data-table-row-actions.tsx
+++ b/ui/components/providers/table/data-table-row-actions.tsx
@@ -27,6 +27,7 @@ import {
type EditScanScheduleState,
} from "@/components/scans/schedule/edit-scan-schedule-modal";
import { useToast } from "@/components/shadcn";
+import { Badge } from "@/components/shadcn/badge/badge";
import {
ActionDropdown,
ActionDropdownDangerZone,
@@ -35,7 +36,10 @@ import {
import { Modal } from "@/components/shadcn/modal";
import { runWithConcurrencyLimit } from "@/lib/concurrency";
import { testProviderConnection } from "@/lib/provider-helpers";
-import { getScanScheduleCapability } from "@/lib/schedules";
+import {
+ canEditProviderAlias,
+ getScanScheduleCapability,
+} from "@/lib/schedules";
import { isCloud } from "@/lib/shared/env";
import { ORG_SETUP_PHASE, ORG_WIZARD_STEP } from "@/types/organizations";
import { PROVIDER_WIZARD_MODE } from "@/types/provider-wizard";
@@ -291,9 +295,11 @@ export function DataTableRowActions({
currentScanConfigId = null,
capability,
}: DataTableRowActionsProps) {
+ const resolvedCapability = capability ?? getScanScheduleCapability(isCloud());
const canEditSchedule =
- (capability ?? getScanScheduleCapability(isCloud())) ===
- SCAN_SCHEDULE_CAPABILITY.ADVANCED;
+ resolvedCapability === SCAN_SCHEDULE_CAPABILITY.ADVANCED;
+ // Editing a provider alias requires a Prowler Cloud subscription
+ const canEditAlias = canEditProviderAlias(resolvedCapability);
const [isEditOpen, setIsEditOpen] = useState(false);
const [isScheduleOpen, setIsScheduleOpen] = useState(false);
const [scheduleState, setScheduleState] = useState({
@@ -590,11 +596,26 @@ export function DataTableRowActions({
)}
- }
- label="Edit Provider Alias"
- onSelect={() => setIsEditOpen(true)}
- />
+ {canEditAlias ? (
+ }
+ label="Edit Provider Alias"
+ onSelect={() => setIsEditOpen(true)}
+ />
+ ) : (
+ }
+ label={
+
+ Edit Provider Alias
+
+ Requires subscription
+
+
+ }
+ disabled
+ />
+ )}
}
label="View Scan Jobs"
diff --git a/ui/lib/schedules.test.ts b/ui/lib/schedules.test.ts
index 279a82881e..f65f0cca1e 100644
--- a/ui/lib/schedules.test.ts
+++ b/ui/lib/schedules.test.ts
@@ -5,6 +5,7 @@ import {
buildScheduleAttributesFromProvider,
buildSchedulesByProviderId,
buildScheduleUpdatePayload,
+ canEditProviderAlias,
formatDayOfMonth,
formatScheduleHour,
getBrowserTimezone,
@@ -440,6 +441,28 @@ describe("scan schedule capability", () => {
});
});
+describe("canEditProviderAlias", () => {
+ it("allows subscribed Cloud accounts (ADVANCED)", () => {
+ expect(canEditProviderAlias(SCAN_SCHEDULE_CAPABILITY.ADVANCED)).toBe(true);
+ });
+
+ it("allows OSS / self-hosted (DAILY_LEGACY, no billing)", () => {
+ expect(canEditProviderAlias(SCAN_SCHEDULE_CAPABILITY.DAILY_LEGACY)).toBe(
+ true,
+ );
+ });
+
+ it("blocks Cloud trial/onboarding without a subscription (MANUAL_ONLY)", () => {
+ expect(canEditProviderAlias(SCAN_SCHEDULE_CAPABILITY.MANUAL_ONLY)).toBe(
+ false,
+ );
+ });
+
+ it("blocks over-limit Cloud accounts (BLOCKED)", () => {
+ expect(canEditProviderAlias(SCAN_SCHEDULE_CAPABILITY.BLOCKED)).toBe(false);
+ });
+});
+
describe("buildSchedulesByProviderId", () => {
const buildSchedule = (
id: string,
diff --git a/ui/lib/schedules.ts b/ui/lib/schedules.ts
index dd4cd0878d..edefb74c19 100644
--- a/ui/lib/schedules.ts
+++ b/ui/lib/schedules.ts
@@ -65,6 +65,19 @@ export function getScanScheduleCapability(
: SCAN_SCHEDULE_CAPABILITY.DAILY_LEGACY;
}
+/**
+ * Whether the current account may edit a provider alias. Alias edits require a
+ * Prowler Cloud subscription
+ */
+export function canEditProviderAlias(
+ capability: ScanScheduleCapability,
+): boolean {
+ return (
+ capability === SCAN_SCHEDULE_CAPABILITY.ADVANCED ||
+ capability === SCAN_SCHEDULE_CAPABILITY.DAILY_LEGACY
+ );
+}
+
export function formatScheduleHour(hour: number): string {
const normalizedHour = ((hour % 24) + 24) % 24;
const period = normalizedHour >= 12 ? "pm" : "am";