Main commit
This commit is contained in:
commit
857c7cf90d
8 changed files with 536 additions and 0 deletions
6
.env.default
Normal file
6
.env.default
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
NODE_ENV=production
|
||||
|
||||
CLOUDFLARE_ZONE_ID=
|
||||
CLOUDFLARE_EMAIL=
|
||||
CLOUDFLARE_API_KEY=
|
||||
ABUSEIPDB_API_KEY=
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.idea
|
||||
node_modules
|
||||
.env
|
||||
reported_ips.csv
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Sefinek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
33
ecosystem.config.js
Normal file
33
ecosystem.config.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
module.exports = {
|
||||
apps: [{
|
||||
name: 'waf-abuseipdb',
|
||||
script: './index.js',
|
||||
|
||||
// Configuration options
|
||||
exec_mode: 'fork',
|
||||
max_memory_restart: '500M',
|
||||
|
||||
// Monitoring changes in files and restarting the application
|
||||
watch: false,
|
||||
ignore_watch: ['.git', 'node_modules', 'logs', 'eslint.config.mjs', 'ecosystem.config.js'],
|
||||
|
||||
// Logging settings
|
||||
log_date_format: 'HH:mm:ss.SSS DD.MM.YYYY',
|
||||
merge_logs: true,
|
||||
log_file: '/home/sefinek/logs/other/waf-abuseipdb/combined.log',
|
||||
out_file: '/home/sefinek/logs/other/waf-abuseipdb/out.log',
|
||||
error_file: '/home/sefinek/logs/other/waf-abuseipdb/error.log',
|
||||
|
||||
// Application restart policy
|
||||
wait_ready: true,
|
||||
autorestart: true,
|
||||
max_restarts: 4,
|
||||
restart_delay: 4000,
|
||||
min_uptime: 3000,
|
||||
|
||||
// Environment variables
|
||||
env: {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
}]
|
||||
};
|
||||
57
eslint.config.mjs
Normal file
57
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import js from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2024,
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.es2024,
|
||||
...globals.browser,
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
'arrow-spacing': ['warn', { before: true, after: true }],
|
||||
'comma-dangle': ['error'],
|
||||
'comma-spacing': 'error',
|
||||
'comma-style': 'error',
|
||||
'curly': ['error', 'multi-line', 'consistent'],
|
||||
'dot-location': ['error', 'property'],
|
||||
'handle-callback-err': 'off',
|
||||
'indent': ['warn', 'tab'],
|
||||
'keyword-spacing': 'warn',
|
||||
'max-nested-callbacks': ['error', { max: 4 }],
|
||||
'max-statements-per-line': ['error', { max: 2 }],
|
||||
'no-console': 'off',
|
||||
'no-empty': 'warn',
|
||||
'no-empty-function': 'error',
|
||||
'no-floating-decimal': 'error',
|
||||
'no-lonely-if': 'error',
|
||||
'no-multi-spaces': 'warn',
|
||||
'no-multiple-empty-lines': ['warn', { max: 4, maxEOF: 1, maxBOF: 0 }],
|
||||
'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }],
|
||||
'no-trailing-spaces': ['warn'],
|
||||
'no-unreachable': 'warn',
|
||||
'no-unused-vars': 'warn',
|
||||
'no-use-before-define': ['error', { functions: false, classes: true }],
|
||||
'no-var': 'error',
|
||||
'object-curly-spacing': ['error', 'always'],
|
||||
'prefer-const': 'error',
|
||||
'quotes': ['warn', 'single'],
|
||||
'semi': ['warn', 'always'],
|
||||
'sort-vars': 'warn',
|
||||
'space-before-blocks': 'error',
|
||||
'space-before-function-paren': ['error', { anonymous: 'never', named: 'never', asyncArrow: 'always' }],
|
||||
'space-in-parens': 'error',
|
||||
'space-infix-ops': 'error',
|
||||
'space-unary-ops': 'error',
|
||||
'spaced-comment': 'warn',
|
||||
'wrap-regex': 'error',
|
||||
'yoda': 'error'
|
||||
},
|
||||
ignores: ['node_modules', 'public/js/bootstrap.bundle.min.js']
|
||||
}
|
||||
];
|
||||
232
index.js
Normal file
232
index.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const {
|
||||
CLOUDFLARE_ZONE_ID,
|
||||
CLOUDFLARE_EMAIL,
|
||||
CLOUDFLARE_API_KEY,
|
||||
ABUSEIPDB_API_KEY,
|
||||
NODE_ENV
|
||||
} = process.env;
|
||||
|
||||
const CSV_FILE_PATH = path.join(__dirname, 'reported_ips.csv');
|
||||
const TIME_WINDOW_MS = 20 * 60 * 1000;
|
||||
const COOLDOWN_MS = 1000;
|
||||
const BLOCK_TIME_MS = 5 * 60 * 60 * 1000; // 5h
|
||||
|
||||
if (!fs.existsSync(CSV_FILE_PATH)) {
|
||||
fs.writeFileSync(CSV_FILE_PATH, 'Timestamp,RayID,IP,Endpoint,Action,Country\n');
|
||||
}
|
||||
|
||||
const PAYLOAD = {
|
||||
query: `query ListFirewallEvents($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) {
|
||||
viewer {
|
||||
zones(filter: { zoneTag: $zoneTag }) {
|
||||
firewallEventsAdaptive(
|
||||
filter: $filter
|
||||
limit: 1000
|
||||
orderBy: [datetime_DESC]
|
||||
) {
|
||||
action
|
||||
clientASNDescription
|
||||
clientAsn
|
||||
clientCountryName
|
||||
clientIP
|
||||
clientRequestHTTPHost
|
||||
clientRequestHTTPMethodName
|
||||
clientRequestHTTPProtocol
|
||||
clientRequestPath
|
||||
clientRequestQuery
|
||||
datetime
|
||||
rayName
|
||||
ruleId
|
||||
source
|
||||
userAgent
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
zoneTag: CLOUDFLARE_ZONE_ID,
|
||||
filter: {
|
||||
datetime_geq: new Date(Date.now() - (60 * 60 * 10.5 * 1000)).toISOString(),
|
||||
datetime_leq: new Date(Date.now() - (60 * 60 * 8 * 1000)).toISOString(),
|
||||
AND: [
|
||||
{ action_neq: 'allow' },
|
||||
{ action_neq: 'skip' },
|
||||
{ action_neq: 'challenge_solved' },
|
||||
{ action_neq: 'challenge_failed' },
|
||||
{ action_neq: 'challenge_bypassed' },
|
||||
{ action_neq: 'jschallenge_solved' },
|
||||
{ action_neq: 'jschallenge_failed' },
|
||||
{ action_neq: 'jschallenge_bypassed' },
|
||||
{ action_neq: 'managed_challenge_skipped' },
|
||||
{ action_neq: 'managed_challenge_non_interactive_solved' },
|
||||
{ action_neq: 'managed_challenge_interactive_solved' },
|
||||
{ action_neq: 'managed_challenge_bypassed' }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${CLOUDFLARE_API_KEY}`,
|
||||
'X-Auth-Email': CLOUDFLARE_EMAIL
|
||||
};
|
||||
|
||||
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.svg', '.webp'];
|
||||
|
||||
const isImageRequest = loc => imageExtensions.some(ext => loc.toLowerCase().endsWith(ext));
|
||||
|
||||
const logToCSV = (timestamp, rayid, ip, endpoint, action, country) => {
|
||||
const logLine = `${timestamp.toISOString()},${rayid},${ip},${endpoint},${action},${country}\n`;
|
||||
fs.appendFileSync(CSV_FILE_PATH, logLine);
|
||||
};
|
||||
|
||||
const logMessage = (level, message) => {
|
||||
const logLevels = {
|
||||
info: '[INFO]',
|
||||
warn: '[WARN]',
|
||||
error: '[FAIL]'
|
||||
};
|
||||
|
||||
const timestamp = NODE_ENV === 'development' ? `${new Date().toISOString()}: ` : '';
|
||||
console[level](`${logLevels[level]} ${timestamp}${message}`);
|
||||
};
|
||||
|
||||
const getBlockedIP = async () => {
|
||||
try {
|
||||
const { data } = await axios.post('https://api.cloudflare.com/client/v4/graphql/', PAYLOAD, { headers });
|
||||
logMessage('info', `Fetched ${data.data.viewer.zones[0].firewallEventsAdaptive.length} events from Cloudflare`);
|
||||
return data;
|
||||
} catch (err) {
|
||||
if (err.response) {
|
||||
logMessage('error', `HTTP Error: ${err.response.status}. Data: ${JSON.stringify(err.response.data, null, 2)}`);
|
||||
} else if (err.request) {
|
||||
logMessage('error', 'No response received from Cloudflare.');
|
||||
} else {
|
||||
logMessage('error', `Request setup error: ${err.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getComment = (it) => `
|
||||
IP: ${it.clientIP} triggered Cloudflare WAF.
|
||||
Action: ${it.action}
|
||||
Source: ${it.source}
|
||||
Client ASN: ${it.clientAsn}
|
||||
Client ASN Description: ${it.clientASNDescription}
|
||||
Client Country: ${it.clientCountryName}
|
||||
HTTP Host: ${it.clientRequestHTTPHost}
|
||||
HTTP Method: ${it.clientRequestHTTPMethodName}
|
||||
HTTP Protocol: ${it.clientRequestHTTPProtocol}
|
||||
HTTP Path: ${it.clientRequestPath}
|
||||
HTTP Query: ${it.clientRequestQuery}
|
||||
Datetime: ${it.datetime}
|
||||
Ray ID: ${it.rayName}
|
||||
Rule ID: ${it.ruleId}
|
||||
User Agent: ${it.userAgent}
|
||||
Report generated by Node-Cloudflare-WAF-To-AbuseIPDB (https://github.com/sefinek24/Node-Cloudflare-WAF-To-AbuseIPDB).
|
||||
`;
|
||||
|
||||
const reportBadIP = async (it, skippedRayIds, blockedIPs) => {
|
||||
const endpoint = `${it.clientRequestHTTPHost}${it.clientRequestPath}`;
|
||||
const country = it.clientCountryName;
|
||||
|
||||
if (isImageRequest(it.clientRequestPath)) {
|
||||
skippedRayIds.add(it.rayName);
|
||||
logToCSV(new Date(), it.rayName, it.clientIP, endpoint, 'Skipped - Image Request', country);
|
||||
logMessage('info', `Skipping IP: ${it.clientIP} for domain: ${it.clientRequestHTTPHost}, Endpoint: ${endpoint} (Image request detected)`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = 'https://api.abuseipdb.com/api/v2/report';
|
||||
const params = {
|
||||
ip: it.clientIP,
|
||||
categories: '19',
|
||||
comment: getComment(it)
|
||||
};
|
||||
|
||||
await axios.post(url, params, { headers: { 'Accept': 'application/json', 'Key': ABUSEIPDB_API_KEY } });
|
||||
logToCSV(new Date(), it.rayName, it.clientIP, endpoint, 'Reported', country);
|
||||
logMessage('info', `Successfully reported IP: ${it.clientIP} for domain: ${it.clientRequestHTTPHost}, Endpoint: ${endpoint}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.response && err.response.status === 429) {
|
||||
blockedIPs.set(it.clientIP, Date.now());
|
||||
logToCSV(new Date(), it.rayName, it.clientIP, endpoint, 'Blocked - 429 Too Many Requests', country);
|
||||
logMessage('warn', `Rate limited (429) while reporting IP: ${it.clientIP}, Domain: ${it.clientRequestHTTPHost}, Endpoint: ${endpoint}. Will retry after 5 hours.`);
|
||||
} else {
|
||||
logMessage('error', `${err.message} - IP: ${it.clientIP}; Domain: ${it.clientRequestHTTPHost}; Endpoint: ${endpoint}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const exceptedRuleId = new Set(['fa01280809254f82978e827892db4e46']);
|
||||
|
||||
const shouldReportDomain = (domain, reportedIPs) => {
|
||||
const lastReport = reportedIPs.find(entry => entry.domain === domain);
|
||||
if (!lastReport) return true;
|
||||
const timeSinceLastReport = Date.now() - lastReport.timestamp.getTime();
|
||||
return timeSinceLastReport > TIME_WINDOW_MS;
|
||||
};
|
||||
|
||||
const shouldSkipBlockedIP = (ip, blockedIPs) => {
|
||||
const lastBlock = blockedIPs.get(ip);
|
||||
if (!lastBlock) return false;
|
||||
const timeSinceLastBlock = Date.now() - lastBlock;
|
||||
return timeSinceLastBlock < BLOCK_TIME_MS;
|
||||
};
|
||||
|
||||
const readReportedIPs = () => {
|
||||
if (!fs.existsSync(CSV_FILE_PATH)) return [];
|
||||
|
||||
const content = fs.readFileSync(CSV_FILE_PATH, 'utf8');
|
||||
return content.split('\n').slice(1).map(line => {
|
||||
const [timestamp, rayid, ip, domain, action, country] = line.split(',');
|
||||
return { timestamp: new Date(timestamp), rayid, ip, domain, action, country };
|
||||
}).filter(entry => entry.timestamp && entry.rayid && entry.ip);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
logMessage('info', 'Starting IP reporting process');
|
||||
const reportedIPs = readReportedIPs();
|
||||
const skippedRayIds = new Set(reportedIPs.filter(ip => ip.action.startsWith('Skipped')).map(ip => ip.rayid));
|
||||
const blockedIPs = new Map(reportedIPs.filter(ip => ip.action.includes('429')).map(ip => [ip.ip, ip.timestamp.getTime()]));
|
||||
|
||||
while (true) {
|
||||
logMessage('info', '===================== New Reporting Cycle =====================');
|
||||
|
||||
const data = await getBlockedIP();
|
||||
|
||||
if (data && data.data) {
|
||||
const ipBadList = data.data.viewer.zones[0].firewallEventsAdaptive;
|
||||
|
||||
for (const i of ipBadList) {
|
||||
const endpoint = `${i.clientRequestHTTPHost}${i.clientRequestPath}`;
|
||||
if (skippedRayIds.has(i.rayName) || shouldSkipBlockedIP(i.clientIP, blockedIPs)) {
|
||||
continue;
|
||||
}
|
||||
if (!exceptedRuleId.has(i.ruleId) && shouldReportDomain(i.clientRequestHTTPHost, reportedIPs)) {
|
||||
const reported = await reportBadIP(i, skippedRayIds, blockedIPs);
|
||||
if (reported) {
|
||||
await new Promise(resolve => setTimeout(resolve, COOLDOWN_MS));
|
||||
}
|
||||
} else if (!skippedRayIds.has(i.rayName)) {
|
||||
skippedRayIds.add(i.rayName);
|
||||
logToCSV(new Date(), i.rayName, i.clientIP, endpoint, 'Skipped - Already Reported', i.clientCountryName);
|
||||
logMessage('info', `Skipping IP: ${i.clientIP} for domain: ${i.clientRequestHTTPHost}, Endpoint: ${endpoint} (Already reported recently)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logMessage('info', '==================== End of Reporting Cycle ====================');
|
||||
await new Promise(resolve => setTimeout(resolve, NODE_ENV === 'production' ? 2 * 60 * 60 * 1000 : 10 * 1000));
|
||||
}
|
||||
})();
|
||||
155
package-lock.json
generated
Normal file
155
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{
|
||||
"name": "node-cf-waf-abuseipdb",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "node-cf-waf-abuseipdb",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.4",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"globals": "^15.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.0.tgz",
|
||||
"integrity": "sha512-hhetes6ZHP3BlXLxmd8K2SNgkhNSi+UcecbnwWKwpP7kyi/uC75DJ1lOOBO3xrC4jyojtGE3YxKZPHfk4yrgug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "15.9.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz",
|
||||
"integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
package.json
Normal file
28
package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "node-cf-waf-abuseipdb",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"up": "ncu -u && npm install && npm update && npm audit fix"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB.git"
|
||||
},
|
||||
"author": "Sefinek <contact@nekosia.cat> (https://sefinek.net)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sefinek24/Node-Cloudflare-WAF-AbuseIPDB#readme",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"globals": "^15.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.4",
|
||||
"dotenv": "^16.4.5"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue