Fix webhook pages in Settings (#10902)

## Context

Some users were able to set an empty URL as webhook targetUrl, which was
breaking the Webhook List and Detail pages

## Fix
- Making sure to protect getHostNameOrThrow by isValidUrl
- rework webhook form to prevent creation of invalid webhooks

Fixes https://github.com/twentyhq/twenty/issues/10822
This commit is contained in:
Charles Bochet
2025-03-14 18:26:28 +01:00
committed by GitHub
parent b47dcba313
commit c833b1c449
2 changed files with 37 additions and 12 deletions

View File

@ -5,7 +5,7 @@ import { IconChevronRight } from 'twenty-ui';
import { Webhook } from '@/settings/developers/types/webhook/Webhook'; import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow'; import { TableRow } from '@/ui/layout/table/components/TableRow';
import { getUrlHostnameOrThrow } from 'twenty-shared'; import { getUrlHostnameOrThrow, isValidUrl } from 'twenty-shared';
export const StyledApisFieldTableRow = styled(TableRow)` export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 1fr 28px; grid-template-columns: 1fr 28px;
@ -39,7 +39,9 @@ export const SettingsDevelopersWebhookTableRow = ({
return ( return (
<StyledApisFieldTableRow to={to}> <StyledApisFieldTableRow to={to}>
<StyledUrlTableCell> <StyledUrlTableCell>
{getUrlHostnameOrThrow(fieldItem.targetUrl)} {isValidUrl(fieldItem.targetUrl)
? getUrlHostnameOrThrow(fieldItem.targetUrl)
: fieldItem.targetUrl}
</StyledUrlTableCell> </StyledUrlTableCell>
<StyledIconTableCell> <StyledIconTableCell>
<StyledIconChevronRight <StyledIconChevronRight

View File

@ -35,7 +35,12 @@ export const useWebhookUpdateForm = ({
const [formData, setFormData] = useState<WebhookFormData>({ const [formData, setFormData] = useState<WebhookFormData>({
targetUrl: '', targetUrl: '',
description: '', description: '',
operations: [], operations: [
{
object: '*',
action: '*',
},
],
secret: '', secret: '',
}); });
@ -98,20 +103,35 @@ export const useWebhookUpdateForm = ({
}); });
}, 300); }, 300);
const validateData = (data: Partial<WebhookFormData>) => { const isFormValidAndSetErrors = () => {
if (isDefined(data?.targetUrl)) { const { targetUrl } = formData;
const trimmedUrl = data.targetUrl.trim();
const isTargetUrlValid = isValidUrl(trimmedUrl); if (isDefined(targetUrl)) {
setIsTargetUrlValid(isTargetUrlValid); const trimmedUrl = targetUrl.trim();
if (isTargetUrlValid) { const isValid = isValidUrl(trimmedUrl);
setTitle(getUrlHostnameOrThrow(trimmedUrl) || 'New Webhook');
if (!isValid) {
setIsTargetUrlValid(false);
return false;
} }
setIsTargetUrlValid(true);
} }
return true;
}; };
const updateWebhook = async (data: Partial<WebhookFormData>) => { const updateWebhook = async (data: Partial<WebhookFormData>) => {
validateData(data);
setFormData((prev) => ({ ...prev, ...data })); setFormData((prev) => ({ ...prev, ...data }));
if (!isFormValidAndSetErrors()) {
return;
}
if (isDefined(data?.targetUrl)) {
setTitle(getUrlHostnameOrThrow(data.targetUrl) || 'New Webhook');
}
await handleSave(); await handleSave();
}; };
@ -173,7 +193,10 @@ export const useWebhookUpdateForm = ({
operations, operations,
secret: data.secret, secret: data.secret,
}); });
setTitle(getUrlHostnameOrThrow(data.targetUrl) || 'New Webhook'); if (isValidUrl(data.targetUrl)) {
setTitle(getUrlHostnameOrThrow(data.targetUrl));
}
setLoading(false); setLoading(false);
}, },
}); });