ip filtering becomes filtering with ja4 and cidrs

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-11-05 21:10:33 +01:00
parent ff5921ec02
commit 691cff9659
12 changed files with 291 additions and 87 deletions
+6 -2
View File
@@ -2657,14 +2657,16 @@ export class API {
* @param {Object} allowdeny
* @param {string} allowdeny.name
* @param {string} allowdeny.cidrs
* @param {string} allowdeny.ja4Fingerprints
* @param {boolean} allowdeny.allowed
* @param {string} allowdeny.companyID
* @returns {Promise<ApiResponse>}
*/
create: async ({ name, cidrs, allowed, companyID }) => {
create: async ({ name, cidrs, ja4Fingerprints, allowed, companyID }) => {
return await postJSON(this.getPath('/allow-deny'), {
name: name,
cidrs: cidrs,
ja4Fingerprints: ja4Fingerprints,
allowed: allowed,
companyID: companyID
});
@@ -2715,13 +2717,15 @@ export class API {
* @param {string} allowdeny.id
* @param {string} allowdeny.name
* @param {string} allowdeny.cidrs
* @param {string} allowdeny.ja4Fingerprints
* @param {string} allowdeny.companyID
* @returns {Promise<ApiResponse>}
*/
update: async ({ id, name, cidrs, companyID }) => {
update: async ({ id, name, cidrs, ja4Fingerprints, companyID }) => {
return await patchJSON(this.getPath(`/allow-deny/${id}`), {
name: name,
cidrs: cidrs,
ja4Fingerprints: ja4Fingerprints,
companyID: companyID
});
},
@@ -80,7 +80,7 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>`,
ip_filters: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
filters: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg>
`,
@@ -138,7 +138,7 @@
'/dashboard/': 'dashboard',
'/campaign/': 'campaigns_overview',
'/campaign-template/': 'campaign_templates',
'/ip-filter/': 'ip_filters',
'/filter/': 'filters',
'/webhook/': 'webhooks',
'/recipient/': 'recipients_overview',
'/recipient/group/': 'recipient_groups',
@@ -107,7 +107,7 @@
'/dashboard/': 'dashboard',
'/campaign/': 'campaigns_overview',
'/campaign-template/': 'campaign_templates',
'/ip-filter/': 'ip_filters',
'/filter/': 'filters',
'/webhook/': 'webhooks',
'/recipient/': 'recipients_overview',
'/recipient/group/': 'recipient_groups',
+2 -2
View File
@@ -78,8 +78,8 @@ export const route = {
route: '/api-sender/'
},
allowDeny: {
label: 'IP filters',
route: '/ip-filter/'
label: 'Filters',
route: '/filter/'
},
webhook: {
label: 'Webhooks',
@@ -1274,7 +1274,7 @@
{#if campaign.allowDeny?.length}
{#each campaign.allowDeny as allowDeny, i}
<a
href="/ip-filter/?edit={allowDeny.id}"
href="/filter/?edit={allowDeny.id}"
class="text-cta-blue dark:text-white hover:underline"
target="_blank"
>
@@ -43,6 +43,7 @@
id: null,
name: null,
cidrs: null,
ja4Fingerprints: null,
allowed: null
};
let allowDenyList = [];
@@ -63,7 +64,7 @@
};
$: {
modalText = getModalText('IP filter', modalMode);
modalText = getModalText('Filter', modalMode);
}
// hooks
@@ -94,7 +95,7 @@
allowDenyList = data.rows;
allowDenyListHasNextPage = data.hasNextPage;
} catch (e) {
addToast('Failed to get IP filters', 'Error');
addToast('Failed to get filters', 'Error');
console.error(e);
} finally {
isTableLoading = false;
@@ -113,8 +114,8 @@
throw res.error;
}
} catch (e) {
addToast('Failed to get IP filter', 'Error');
console.error('failed to get IP filter', e);
addToast('Failed to get filter', 'Error');
console.error('failed to get filter', e);
}
};
@@ -126,13 +127,22 @@
}
return res.data;
} catch (e) {
addToast('Failed to get IP filters', 'Error');
console.error('failed to get IP filters', e);
addToast('Failed to get filters', 'Error');
console.error('failed to get filters', e);
}
return [];
};
const onClickSubmit = async () => {
// validate that at least one of cidrs or ja4Fingerprints is provided
const hasCidrs = formValues.cidrs && formValues.cidrs.trim().length > 0;
const hasJA4 = formValues.ja4Fingerprints && formValues.ja4Fingerprints.trim().length > 0;
if (!hasCidrs && !hasJA4) {
formError = 'At least one of CIDRs or JA4 fingerprints must be provided';
return;
}
try {
isSubmitting = true;
if (modalMode === 'create' || modalMode === 'copy') {
@@ -149,16 +159,23 @@
const create = async () => {
formError = '';
formValues.cidrs = formValues.cidrs
.split('\n')
.map((line) => singleIPToCIDR(line))
.filter((line) => line.length > 0)
.join('\n');
// process cidrs only if provided
if (formValues.cidrs) {
formValues.cidrs = formValues.cidrs
.split('\n')
.map((line) => singleIPToCIDR(line))
.filter((line) => line.length > 0)
.join('\n');
} else {
formValues.cidrs = '';
}
try {
const res = await api.allowDeny.create({
name: formValues.name,
cidrs: formValues.cidrs,
ja4Fingerprints: formValues.ja4Fingerprints || '',
allowed: formValues.allowed,
companyID: contextCompanyID
});
@@ -166,39 +183,46 @@
formError = res.error;
return;
}
addToast('Created IP filter', 'Success');
addToast('Created filter', 'Success');
closeModal();
} catch (err) {
addToast('Failed to create IP filter', 'Error');
console.error('failed to create IP filter:', err);
addToast('Failed to create filter', 'Error');
console.error('failed to create filter:', err);
}
refreshAllowDenies();
};
const update = async () => {
formError = '';
formValues.cidrs = formValues.cidrs
.split('\n')
.map((line) => singleIPToCIDR(line))
.filter((line) => line.length > 0)
.join('\n');
// process cidrs only if provided
if (formValues.cidrs) {
formValues.cidrs = formValues.cidrs
.split('\n')
.map((line) => singleIPToCIDR(line))
.filter((line) => line.length > 0)
.join('\n');
} else {
formValues.cidrs = '';
}
try {
const res = await api.allowDeny.update({
id: formValues.id,
name: formValues.name,
cidrs: formValues.cidrs,
ja4Fingerprints: formValues.ja4Fingerprints || '',
companyID: formValues.companyID
});
if (res.success) {
addToast('Updated IP filter', 'Success');
addToast('Updated filter', 'Success');
closeModal();
} else {
formError = res.error;
}
} catch (e) {
addToast('Failed to update IP filter', 'Error');
console.error('failed to update IP filter', e);
addToast('Failed to update filter', 'Error');
console.error('failed to update filter', e);
}
refreshAllowDenies();
};
@@ -223,7 +247,7 @@
throw res.error;
})
.catch((e) => {
console.error('failed to delete IP filter:', e);
console.error('failed to delete filter:', e);
});
return action;
};
@@ -257,8 +281,8 @@
assignAllowDeny(allowDeny);
isModalVisible = true;
} catch (e) {
addToast('Failed to get IP filter', 'Error');
console.error('failed to get IP filter', e);
addToast('Failed to get filter', 'Error');
console.error('failed to get filter', e);
} finally {
hideIsLoading();
}
@@ -274,8 +298,8 @@
allowDeny.id = null;
isModalVisible = true;
} catch (e) {
addToast('Failed to get IP filter', 'Error');
console.error('failed to get IP filter', e);
addToast('Failed to get filter', 'Error');
console.error('failed to get filter', e);
} finally {
hideIsLoading();
}
@@ -286,6 +310,7 @@
id: allowDeny.id,
name: allowDeny.name,
cidrs: allowDeny.cidrs,
ja4Fingerprints: allowDeny.ja4Fingerprints || '',
allowed: allowDeny.allowed,
companyID: allowDeny.companyID
};
@@ -320,9 +345,9 @@
};
</script>
<HeadTitle title="IP filter" />
<HeadTitle title="Filter" />
<main>
<Headline>IP filters</Headline>
<Headline>Filters</Headline>
<BigButton on:click={openCreateModal}>New filter</BigButton>
<Table
columns={[
@@ -402,12 +427,21 @@
/>
{/if}
<TextareaField
required
minLength="1"
optional
bind:value={formValues.cidrs}
placeholder="8.8.8.8/16"
toolTipText="Newlines seperated CIDRs">CIDRs</TextareaField
toolTipText="Newlines seperated CIDRs (optional)">CIDRs</TextareaField
>
<TextareaField
optional
bind:value={formValues.ja4Fingerprints}
placeholder="t13d1715h2_8daaf6152771_02713d6af862"
toolTipText="Newlines separated JA4 fingerprints (optional)"
>JA4 Fingerprints</TextareaField
>
<p style="font-size: 0.875rem; color: #666; margin-top: 0.5rem;">
Note: At least one of CIDRs or JA4 Fingerprints must be provided.
</p>
</FormColumn>
</FormColumns>
<FormError message={formError} />