mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-06-17 04:52:05 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f78e420938 | |||
| 262dfda0aa | |||
| 8bc42a5ded |
@@ -1,7 +1,7 @@
|
||||
# Build command
|
||||
# docker build --platform=linux/amd64 --no-cache -t prowler:latest .
|
||||
|
||||
ARG PROWLER_VERSION=latest@sha256:4b796c6df40a3350c7947747b59bdda230d0da6222287500e13b0a8e1574aad4
|
||||
ARG PROWLER_VERSION=latest@sha256:f61e0dfb4b07eebdef884392955ba799b9c77d321ced1578bfd403814f4aec8d
|
||||
|
||||
FROM toniblyx/prowler:${PROWLER_VERSION}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions [(#11523)](https://github.com/prowler-cloud/prowler/pull/11523)
|
||||
- `cosmosdb_account_automatic_failover_enabled` check for Azure provider [(#11031)](https://github.com/prowler-cloud/prowler/pull/11031)
|
||||
- `cosmosdb_account_backup_policy_continuous` check for Azure provider [(#11032)](https://github.com/prowler-cloud/prowler/pull/11032)
|
||||
- `cosmosdb_account_minimum_tls_version` check for Azure provider, verifying Cosmos DB accounts enforce TLS 1.2 or higher for client connections [(#11033)](https://github.com/prowler-cloud/prowler/pull/11033)
|
||||
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
|
||||
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
|
||||
- TLS certificate verification in the `codepipeline_project_repo_private` check, which previously used an unverified SSL context, leaving the repository-visibility probe open to MITM tampering [(#11603)](https://github.com/prowler-cloud/prowler/pull/11603)
|
||||
|
||||
@@ -1503,6 +1503,7 @@
|
||||
],
|
||||
"Checks": [
|
||||
"app_minimum_tls_version_12",
|
||||
"cosmosdb_account_minimum_tls_version",
|
||||
"monitor_storage_account_with_activity_logs_cmk_encrypted",
|
||||
"sqlserver_tde_encrypted_with_cmk",
|
||||
"sqlserver_tde_encryption_enabled",
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "cosmosdb_account_minimum_tls_version",
|
||||
"CheckTitle": "Cosmos DB account enforces TLS 1.2 or higher",
|
||||
"CheckType": [],
|
||||
"ServiceName": "cosmosdb",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "microsoft.documentdb/databaseaccounts",
|
||||
"ResourceGroup": "database",
|
||||
"Description": "**Azure Cosmos DB accounts** are evaluated for **minimum TLS version**. TLS 1.0 and 1.1 are deprecated and contain known weaknesses. Enforcing **TLS 1.2** ensures all client connections negotiate modern, secure encryption protocols.",
|
||||
"Risk": "Allowing **TLS 1.0/1.1** exposes client connections to **POODLE**, **BEAST**, and other protocol downgrade attacks that can compromise the **confidentiality** and **integrity** of data in transit, and may enable credential interception via weakened cipher suites.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/azure/cosmos-db/security",
|
||||
"https://learn.microsoft.com/en-us/cli/azure/cosmosdb",
|
||||
"https://learn.microsoft.com/en-us/azure/templates/microsoft.documentdb/databaseaccounts"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "az cosmosdb update --name <COSMOS_ACCOUNT_NAME> --resource-group <RESOURCE_GROUP> --minimal-tls-version Tls12",
|
||||
"NativeIaC": "```bicep\n// Bicep: Enforce minimum TLS 1.2 on a Cosmos DB account\nresource account 'Microsoft.DocumentDB/databaseAccounts@2025-10-15' = {\n name: '<example_resource_name>'\n location: resourceGroup().location\n kind: 'GlobalDocumentDB'\n properties: {\n databaseAccountOfferType: 'Standard'\n locations: [{ locationName: resourceGroup().location }]\n minimalTlsVersion: 'Tls12' // Critical: Rejects client connections negotiating TLS 1.0 or 1.1\n }\n}\n```",
|
||||
"Other": "1. Sign in to the Azure portal and open your Cosmos DB account\n2. In the left menu, select Networking\n3. Locate the Minimum TLS version setting\n4. Select TLS 1.2\n5. Click Save\n6. Validate that all client applications support TLS 1.2 before enforcing the change",
|
||||
"Terraform": "```hcl\n# Terraform: Enforce minimum TLS 1.2 on a Cosmos DB account\nresource \"azurerm_cosmosdb_account\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n resource_group_name = \"<example_resource_name>\"\n location = \"<example_location>\"\n offer_type = \"Standard\"\n kind = \"GlobalDocumentDB\"\n\n geo_location {\n location = \"<example_location>\"\n failover_priority = 0\n }\n\n minimal_tls_version = \"Tls12\" # Critical: Rejects client connections negotiating TLS 1.0 or 1.1\n}\n```"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Set the Cosmos DB account **minimum TLS version** to at least **1.2** to block legacy protocols (`TLS 1.0`/`1.1`) vulnerable to known downgrade and cipher attacks. Inventory and update **client SDKs** and **drivers** to support TLS 1.2 prior to enforcement, and pair this control with **private endpoints**, **AAD/RBAC authentication**, and **handshake failure monitoring** to identify outdated clients.",
|
||||
"Url": "https://hub.prowler.com/check/cosmosdb_account_minimum_tls_version"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"encryption"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
from prowler.lib.check.models import Check, Check_Report_Azure
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_client import cosmosdb_client
|
||||
|
||||
|
||||
class cosmosdb_account_minimum_tls_version(Check):
|
||||
"""Ensure that Cosmos DB accounts enforce TLS 1.2 or higher."""
|
||||
|
||||
def execute(self) -> Check_Report_Azure:
|
||||
"""Execute the Cosmos DB minimum TLS version check.
|
||||
|
||||
Iterates over every Cosmos DB account fetched by the service and reports
|
||||
PASS when `minimalTlsVersion` is `Tls12` or higher, FAIL otherwise
|
||||
(including when the property is missing or set to a legacy value).
|
||||
|
||||
Returns:
|
||||
A list of Check_Report_Azure with one report per Cosmos DB account.
|
||||
"""
|
||||
findings = []
|
||||
for subscription, accounts in cosmosdb_client.accounts.items():
|
||||
for account in accounts:
|
||||
report = Check_Report_Azure(metadata=self.metadata(), resource=account)
|
||||
report.subscription = subscription
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"CosmosDB account {account.name} from subscription {subscription} does not enforce TLS 1.2 or higher."
|
||||
if account.minimal_tls_version in {"Tls12", "Tls13"}:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"CosmosDB account {account.name} from subscription {subscription} enforces TLS 1.2 or higher."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
from unittest import mock
|
||||
|
||||
from tests.providers.azure.azure_fixtures import (
|
||||
AZURE_SUBSCRIPTION_ID,
|
||||
set_mocked_azure_provider,
|
||||
)
|
||||
|
||||
|
||||
class Test_cosmosdb_account_minimum_tls_version:
|
||||
def test_no_subscriptions(self):
|
||||
cosmosdb_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version.cosmosdb_client",
|
||||
new=cosmosdb_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version import (
|
||||
cosmosdb_account_minimum_tls_version,
|
||||
)
|
||||
|
||||
cosmosdb_client.accounts = {}
|
||||
|
||||
check = cosmosdb_account_minimum_tls_version()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
def test_pass_tls12(self):
|
||||
cosmosdb_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version.cosmosdb_client",
|
||||
new=cosmosdb_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version import (
|
||||
cosmosdb_account_minimum_tls_version,
|
||||
)
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
|
||||
Account,
|
||||
)
|
||||
|
||||
cosmosdb_client.accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name="test-account",
|
||||
kind="GlobalDocumentDB",
|
||||
type="Microsoft.DocumentDB/databaseAccounts",
|
||||
tags={},
|
||||
is_virtual_network_filter_enabled=False,
|
||||
location="eastus",
|
||||
private_endpoint_connections=[],
|
||||
disable_local_auth=False,
|
||||
minimal_tls_version="Tls12",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
check = cosmosdb_account_minimum_tls_version()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].status_extended == (
|
||||
f"CosmosDB account test-account from subscription "
|
||||
f"{AZURE_SUBSCRIPTION_ID} enforces TLS 1.2 or higher."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
|
||||
def test_pass_tls13(self):
|
||||
cosmosdb_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version.cosmosdb_client",
|
||||
new=cosmosdb_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version import (
|
||||
cosmosdb_account_minimum_tls_version,
|
||||
)
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
|
||||
Account,
|
||||
)
|
||||
|
||||
cosmosdb_client.accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name="test-account",
|
||||
kind="GlobalDocumentDB",
|
||||
type="Microsoft.DocumentDB/databaseAccounts",
|
||||
tags={},
|
||||
is_virtual_network_filter_enabled=False,
|
||||
location="eastus",
|
||||
private_endpoint_connections=[],
|
||||
disable_local_auth=False,
|
||||
minimal_tls_version="Tls13",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
check = cosmosdb_account_minimum_tls_version()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
def test_fail_tls11(self):
|
||||
cosmosdb_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version.cosmosdb_client",
|
||||
new=cosmosdb_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version import (
|
||||
cosmosdb_account_minimum_tls_version,
|
||||
)
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
|
||||
Account,
|
||||
)
|
||||
|
||||
cosmosdb_client.accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name="test-account",
|
||||
kind="GlobalDocumentDB",
|
||||
type="Microsoft.DocumentDB/databaseAccounts",
|
||||
tags={},
|
||||
is_virtual_network_filter_enabled=False,
|
||||
location="eastus",
|
||||
private_endpoint_connections=[],
|
||||
disable_local_auth=False,
|
||||
minimal_tls_version="Tls11",
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
check = cosmosdb_account_minimum_tls_version()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].status_extended == (
|
||||
f"CosmosDB account test-account from subscription "
|
||||
f"{AZURE_SUBSCRIPTION_ID} does not enforce TLS 1.2 or higher."
|
||||
)
|
||||
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
|
||||
|
||||
def test_fail_no_tls_version(self):
|
||||
cosmosdb_client = mock.MagicMock()
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_azure_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version.cosmosdb_client",
|
||||
new=cosmosdb_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_account_minimum_tls_version.cosmosdb_account_minimum_tls_version import (
|
||||
cosmosdb_account_minimum_tls_version,
|
||||
)
|
||||
from prowler.providers.azure.services.cosmosdb.cosmosdb_service import (
|
||||
Account,
|
||||
)
|
||||
|
||||
cosmosdb_client.accounts = {
|
||||
AZURE_SUBSCRIPTION_ID: [
|
||||
Account(
|
||||
id="/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
|
||||
name="test-account",
|
||||
kind="GlobalDocumentDB",
|
||||
type="Microsoft.DocumentDB/databaseAccounts",
|
||||
tags={},
|
||||
is_virtual_network_filter_enabled=False,
|
||||
location="eastus",
|
||||
private_endpoint_connections=[],
|
||||
disable_local_auth=False,
|
||||
minimal_tls_version=None,
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
check = cosmosdb_account_minimum_tls_version()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
@@ -701,6 +701,31 @@ describe("AlertFormModal", () => {
|
||||
expect(errorMessage).toHaveClass("text-text-error-primary");
|
||||
});
|
||||
|
||||
it("should clear the preview error when save shows the form error", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
alertsActionMocks.seedAlertRule.mockResolvedValue({
|
||||
error: "No alert-compatible filters",
|
||||
});
|
||||
mockRecipientsList();
|
||||
renderCreateModal({ editingAlert: createEditingAlert() });
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: /^test$/i }));
|
||||
expect(await screen.findByText("Test result")).toBeVisible();
|
||||
await user.click(screen.getByRole("button", { name: /^save$/i }));
|
||||
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText("Test result")).not.toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
screen.getAllByText(
|
||||
"Apply at least one alert-compatible Findings filter.",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should hydrate advanced edit mode filters and normalize them on save", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { isValidElement, type ReactNode } from "react";
|
||||
import { isValidElement, type ReactNode, useState } from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
ALERT_TRIGGER_KINDS,
|
||||
type AlertRule,
|
||||
} from "@/app/(prowler)/alerts/_types";
|
||||
import type {
|
||||
AlertFormSubmitResult,
|
||||
AlertFormValues,
|
||||
} from "@/app/(prowler)/alerts/_types/alert-form";
|
||||
|
||||
import { AlertsManager } from "../alerts-manager";
|
||||
|
||||
@@ -96,12 +100,32 @@ vi.mock("../alert-form-modal", () => ({
|
||||
open,
|
||||
editingAlert,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
open: boolean;
|
||||
editingAlert?: AlertRule | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) =>
|
||||
open ? (
|
||||
onSubmit: (values: AlertFormValues) => Promise<AlertFormSubmitResult>;
|
||||
}) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const submit = async () => {
|
||||
const result = await onSubmit({
|
||||
name: "Updated alert",
|
||||
description: "",
|
||||
method: "email",
|
||||
frequency: ALERT_TRIGGER_KINDS.AFTER_SCAN,
|
||||
condition: {
|
||||
op: ALERT_AGGREGATE_OPS.ANY,
|
||||
filter: { severity: ["critical"] },
|
||||
},
|
||||
recipientEmails: [],
|
||||
enabled: true,
|
||||
});
|
||||
setError(result.ok ? null : (result.error ?? null));
|
||||
};
|
||||
|
||||
return open ? (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-label={editingAlert ? "Edit Alert" : "Create Alert"}
|
||||
@@ -109,9 +133,14 @@ vi.mock("../alert-form-modal", () => ({
|
||||
<button type="button" onClick={() => onOpenChange(false)}>
|
||||
Close modal
|
||||
</button>
|
||||
<button type="button" onClick={submit}>
|
||||
Submit alert
|
||||
</button>
|
||||
{editingAlert?.attributes.name}
|
||||
{error && <p>{error}</p>}
|
||||
</div>
|
||||
) : null,
|
||||
) : null;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../alerts-empty-state", () => ({
|
||||
@@ -259,6 +288,42 @@ describe("AlertsManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows a manage alerts permission message for edit 403 errors", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
const alert = makeAlert(true);
|
||||
actionMocks.updateAlert.mockResolvedValue({
|
||||
error: "You do not have permission to perform this action.",
|
||||
status: 403,
|
||||
});
|
||||
render(
|
||||
<AlertsManager
|
||||
alerts={[alert]}
|
||||
loadError={null}
|
||||
providers={[]}
|
||||
completedScanIds={[]}
|
||||
scanDetails={[]}
|
||||
uniqueRegions={[]}
|
||||
uniqueServices={[]}
|
||||
uniqueResourceTypes={[]}
|
||||
uniqueCategories={[]}
|
||||
uniqueGroups={[]}
|
||||
initialEditingAlert={alert}
|
||||
/>,
|
||||
);
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: /submit alert/i }));
|
||||
|
||||
// Then
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"You don't have permission to manage alerts. Ask an administrator to update your role.",
|
||||
),
|
||||
).toBeVisible();
|
||||
expect(toastMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows a success toast after disabling an alert", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -451,6 +451,7 @@ const AlertFormModalContent = ({
|
||||
? await seedAlertRule(pendingFilters)
|
||||
: null;
|
||||
if (seedResult?.error) {
|
||||
setPreview(null);
|
||||
setErrors({ root: ALERT_SEED_ERROR });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ interface AlertsManagerProps {
|
||||
|
||||
const ALERTS_FINDINGS_HREF =
|
||||
"/findings?filter[muted]=false&filter[status__in]=FAIL";
|
||||
const ALERTS_PERMISSION_ERROR =
|
||||
"You don't have permission to manage alerts. Ask an administrator to update your role.";
|
||||
|
||||
export const AlertsManager = ({
|
||||
alerts,
|
||||
@@ -108,7 +110,12 @@ export const AlertsManager = ({
|
||||
}
|
||||
const payload = toAlertPayload(values);
|
||||
const result = await updateAlert(editingAlert.id, payload);
|
||||
if (result?.error) return { ok: false, error: result.error };
|
||||
if (result?.error) {
|
||||
return {
|
||||
ok: false,
|
||||
error: result.status === 403 ? ALERTS_PERMISSION_ERROR : result.error,
|
||||
};
|
||||
}
|
||||
toast({
|
||||
title: "Alert updated",
|
||||
description: result.data.attributes.name,
|
||||
|
||||
Reference in New Issue
Block a user