I’m working on a firewall management web application where I can add various filters. I have set up the backend using Node.js and Express, and I’m using an external API to manage the firewall rules and filters.
Here are the details of my setup:
Backend: Node.js with Express.
Frontend: HTML, JavaScript.
External API: [API documentation](https://apidoc.dsh.gg).
Problem
When I try to add a new filter using the frontend form, I receive an error indicating that some required fields are not recognized, even though they are included in the request.
Error Message:
`{ status: "Internal Server Error", statusCode: 500, message: "Filter could not be added, error: Field 'port' does not exist but is required!nField 'max_conn_pps' does not exist but is required!" }
`
Backend Code (server.js)
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const path = require('path');
const app = express();
const port = 3000;
const apiUrl = 'https://api.dsh.gg/api/v2';
const token = 'DSH-53526-dUSam7bqCoNKWUocLFuGf2ob6Qkuw8CjTUP2XQhAhqxLZqjdzEm6v3CS3CWkQ2kaAJB6RFXTCU8kw5owfadThud3TxLs3AZfXM2MAWUxAB7VQs8vDxm8w6QqdTgLvY7r';
const ipAddressForRulesAndFilters = '5.252.100.142'; // Existing IP address for rules and filters
const ipAddressForAttackHistory = '5.252.101.219'; // New IP address for attack history
app.use(cors());
app.use(express.json());
app.use(express.static('public')); // Serve static files from the "public" directory
// Log each request to the server for debugging
app.use((req, res, next) => {
console.log(`${req.method} request for '${req.url}'`);
next();
});
// Serve the main page
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Serve the rules page
app.get('/rules', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'rules.html'));
});
// Serve the filters page
app.get('/filters', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'filters.html'));
});
// Serve the attacks page
app.get('/attacks', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'attacks.html'));
});
// Route to fetch attack history
app.get('/api/attacks', async (req, res) => {
try {
const response = await axios.get(`${apiUrl}/protection/incidents/${ipAddressForAttackHistory}`, {
headers: {
'x-token': token
}
});
console.log('Attack history response data:', response.data); // Debug log
res.json(response.data);
} catch (error) {
console.error('Error fetching attack history:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to list firewall rules
app.get('/api/firewall/rules', async (req, res) => {
try {
const response = await axios.get(`${apiUrl}/protection/rules/${ipAddressForRulesAndFilters}`, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error fetching rules:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to list filters
app.get('/api/firewall/filters', async (req, res) => {
try {
const response = await axios.get(`${apiUrl}/protection/filters/${ipAddressForRulesAndFilters}`, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error fetching filters:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to fetch available filters
app.get('/api/available-filters', async (req, res) => {
try {
const response = await axios.get(`${apiUrl}/protection/filters/available`, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error fetching available filters:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to add a new filter
app.post('/api/firewall/filters', async (req, res) => {
const { type, settings } = req.body;
try {
const response = await axios.post(`${apiUrl}/protection/filters/${ipAddressForRulesAndFilters}`, {
type,
settings
}, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error adding filter:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to add a new firewall rule
app.post('/api/firewall/rules', async (req, res) => {
const rule = req.body;
try {
const response = await axios.post(`${apiUrl}/protection/rules/${ipAddressForRulesAndFilters}`, rule, {
headers: {
'x-token': token,
'Content-Type': 'application/json'
}
});
res.json(response.data);
} catch (error) {
console.error('Error adding rule:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to delete a firewall rule by ID
app.delete('/api/firewall/rules/:id', async (req, res) => {
const ruleId = req.params.id;
try {
const response = await axios.delete(`${apiUrl}/protection/rules/${ipAddressForRulesAndFilters}/${ruleId}`, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error deleting rule:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
// Route to delete a filter by ID
app.delete('/api/firewall/filters/:id', async (req, res) => {
const filterId = req.params.id;
try {
const response = await axios.delete(`${apiUrl}/protection/filters/${ipAddressForRulesAndFilters}/${filterId}`, {
headers: {
'x-token': token
}
});
res.json(response.data);
} catch (error) {
console.error('Error deleting filter:', error);
res.status(500).json({ error: error.response ? error.response.data : error.message });
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
Frontend Code (filters.js)
document.addEventListener('DOMContentLoaded', () => {
fetchFilters();
fetchAvailableFilters();
});
let filtersPerPage = 10;
let currentPage = 1;
let filtersData = [];
let availableFilters = [];
const managedIPAddress = '5.252.100.142';
async function fetchFilters() {
try {
const response = await fetch('/api/firewall/filters');
const data = await response.json();
filtersData = data.items || [];
displayFilters();
setupPagination();
} catch (error) {
console.error('Error fetching filters:', error);
}
}
function displayFilters() {
const filtersTable = document.getElementById('filtersTable').querySelector('tbody');
filtersTable.innerHTML = '';
const start = (currentPage - 1) * filtersPerPage;
const end = filtersPerPage === 'all' ? filtersData.length : start + filtersPerPage;
const filtersToDisplay = filtersData.slice(start, end);
filtersToDisplay.forEach(filter => {
const row = document.createElement('tr');
const settings = filter.settings
? Object.keys(filter.settings).map(key => {
if (filter.settings[key] !== undefined && filter.settings[key] !== null) {
return `<span class="settings-badge">${key.toUpperCase()}: ${filter.settings[key]}</span>`;
}
return '';
}).join('')
: 'No settings available';
row.innerHTML = `
<td><input type="checkbox" class="filter-checkbox" data-id="${filter.id}" onclick="toggleDeleteCheckedButton()"></td>
<td>${filter.id}</td>
<td>${filter.filter_type}</td>
<td>${settings}</td>
<td><button class="delete-button" onclick="deleteFilter(${filter.id})">Delete</button></td>
`;
filtersTable.appendChild(row);
});
}
function setupPagination() {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
if (filtersPerPage === 'all') return;
const pageCount = Math.ceil(filtersData.length / filtersPerPage);
for (let i = 1; i <= pageCount; i++) {
const button = document.createElement('button');
button.textContent = i;
button.classList.toggle('active', i === currentPage);
button.addEventListener('click', () => {
currentPage = i;
displayFilters();
setupPagination();
});
pagination.appendChild(button);
}
}
async function fetchAvailableFilters() {
try {
const response = await fetch('/api/available-filters', {
headers: { 'x-token': 'YOUR_TOKEN_HERE' }
});
if (response.ok) {
const data = await response.json();
availableFilters = data.items || [];
populateFilterTypeOptions();
} else {
console.error('Error fetching available filters:', response.statusText);
}
} catch (error) {
console.error('Error fetching available filters:', error);
}
}
function populateFilterTypeOptions() {
const filterTypeSelect = document.getElementById('filterType');
filterTypeSelect.innerHTML = '';
availableFilters.forEach(filter => {
const option = document.createElement('option');
option.value = filter.name;
option.textContent = filter.label;
filterTypeSelect.appendChild(option);
});
filterTypeSelect.addEventListener('change', populateFilterFields);
}
function populateFilterFields() {
const selectedFilterName = document.getElementById('filterType').value;
const selectedFilter = availableFilters.find(filter => filter.name === selectedFilterName);
const filterFieldsContainer = document.getElementById('filterFields');
filterFieldsContainer.innerHTML = '';
if (selectedFilter && selectedFilter.fields) {
selectedFilter.fields.forEach(field => {
const formGroup = document.createElement('div');
formGroup.classList.add('form-group');
const label = document.createElement('label');
label.textContent = field.label;
formGroup.appendChild(label);
let input;
switch (field.value.type) {
case 'cidr':
case 'ip':
input = document.createElement('input');
input.type = 'text';
input.placeholder = field.example || '';
input.value = managedIPAddress;
break;
case 'port':
input = document.createElement('input');
input.type = 'text';
input.placeholder = field.example || '';
break;
case 'bool':
input = document.createElement('input');
input.type = 'checkbox';
input.checked = field.value.default || false;
break;
case 'integer':
input = document.createElement('input');
input.type = 'number';
input.min = field.value.min || 0;
input.max = field.value.max || 1000000000;
break;
case 'select':
input = document.createElement('select');
field.value.options.forEach(option => {
const selectOption = document.createElement('option');
selectOption.value = option.value;
selectOption.textContent = option.label;
input.appendChild(selectOption);
});
break;
}
input.id = field.name;
input.classList.add('filter-field');
formGroup.appendChild(input);
filterFieldsContainer.appendChild(formGroup);
});
}
}
async function addFilter() {
const selectedFilterName = document.getElementById('filterType').value;
const selectedFilter = availableFilters.find(filter => filter.name === selectedFilterName);
const filterFields = document.querySelectorAll('.filter-field');
const filterData = {};
filterFields.forEach(field => {
if (field.type === 'checkbox') {
filterData[field.id] = field.checked;
} else {
filterData[field.id] = field.value;
}
});
// Check if all required fields are present
const missingFields = selectedFilter.fields.filter(field => !filterData[field.name]);
if (missingFields.length > 0) {
alert(`The following fields are required: ${missingFields.map(field => field.label).join(', ')}`);
return;
}
console.log('Submitting filter data:', { type: selectedFilter.name, settings: filterData });
try {
const response = await fetch('/api/firewall/filters', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-token': 'YOUR_TOKEN_HERE'
},
body: JSON.stringify({
type: selectedFilter.name,
settings: filterData
})
});
const data = await response.json();
if (response.ok) {
console.log('Filter added successfully:', data);
closeAddFilterModal();
fetchFilters();
} else {
console.error('Error adding filter:', data);
}
} catch (error) {
console.error('Error adding filter:', error);
}
}
function openAddFilterModal() {
document.getElementById('addFilterModal').style.display = 'block';
}
function closeAddFilterModal() {
document.getElementById('addFilterModal').style.display = 'none';
}
Additional Information:
The required fields for each filter type are included in the response from the /api/available-filters endpoint.
The frontend dynamically generates the form based on the selected filter type and its required fields.
Question
Why am I receiving errors that required fields are missing, even though they are present in the request? How can I resolve this issue to successfully add filters?
What did you try and what were you expecting?
I implemented a dynamic form in the frontend that retrieves the available filter types and their required fields from the API. When a filter type is selected, the form is populated with the appropriate fields, and I fill in the necessary values.
Backend:
I set up endpoints in server.js to handle fetching available filters, fetching existing filters, and adding new filters using the external API.
I used Axios to make the API requests with the required headers, including the API token.
Frontend:
The fetchAvailableFilters function retrieves the available filter types and their required fields.
The populateFilterFields function dynamically creates form inputs based on the selected filter type.
The addFilter function collects the form data and sends a POST request to the backend, which forwards it to the external API.
I expected that when I submit the form with all the required fields filled out, the filter would be successfully added. However, I am encountering an error that states certain required fields are missing, even though they are included in the request payload.
I verified that the request payload contains the required fields and values as specified by the API documentation, but the error persists.
Here is a sample of what the request payload looks like when I attempt to add a filter:
{
type: “tcp_symmetric”,
settings: {
addr: “5.252.100.142”,
port: “587”,
max_conn_pps: “1000”
}
}
Despite this, I receive the following error response:
{ status: “Internal Server Error”, statusCode: 500, message: “Filter could not be added, error: Field ‘port’ does not exist but is required!nField ‘max_conn_pps’ does not exist but is required!” }
I am looking for guidance on what might be causing this issue and how to ensure the required fields are recognized and processed correctly by the API.