From 40ecef5ec43b9562e9f09ec8e808de5c0183e786 Mon Sep 17 00:00:00 2001 From: coderiaser Date: Tue, 26 May 2026 22:06:44 +0300 Subject: [PATCH] feature: cloudcmd: ratelimit: X-Forwarded-For (#437) --- .madrun.js | 3 ++- .npmignore | 2 +- bin/cloudcmd.js | 3 ++- package.json | 1 + server/server.js | 17 +++++++++++------ test-e2e/ratelimit.js | 44 +++++++++++++++++++++++++++++++++++++++++++ test/e2e/ratelimit.js | 22 ---------------------- 7 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 test-e2e/ratelimit.js delete mode 100644 test/e2e/ratelimit.js diff --git a/.madrun.js b/.madrun.js index 9bd30e3d..7452328c 100644 --- a/.madrun.js +++ b/.madrun.js @@ -28,7 +28,8 @@ export default { 'lint:fresh': () => run('lint', '--fresh'), 'fix:lint': async () => `putout --rulesdir rules --fix . && redlint fix`, 'lint:stream': () => run('lint', '-f stream'), - 'test': () => [testEnv, `tape 'test/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], + 'test': () => [testEnv, `tape '{test,test-e2e}/**/*.js' '{bin,client,static,common,server}/**/*.spec.js' -f fail`], + 'test:e2e': () => `tape 'test-e2e/**/*.js'`, 'test:client': () => `tape 'test/client/**/*.js'`, 'test:server': () => `tape 'test/**/*.js' 'server/**/*.spec.js' 'common/**/*.spec.js'`, 'wisdom': async () => await run(['lint:all', 'build', 'test'], null, { diff --git a/.npmignore b/.npmignore index 04da926f..b83b5923 100644 --- a/.npmignore +++ b/.npmignore @@ -20,7 +20,7 @@ img/logo/cloudcmd-hq.png webpack.config.js docker -test +test* fixture fixture-* coverage diff --git a/bin/cloudcmd.js b/bin/cloudcmd.js index 6f057a1f..6ca6716c 100755 --- a/bin/cloudcmd.js +++ b/bin/cloudcmd.js @@ -264,7 +264,8 @@ function validateRoot(root, config) { if (root === '/') return; - console.log(`root: ${root}`); + if (config('log')) + console.log(`root: ${root}`); } async function getPassword(password) { diff --git a/package.json b/package.json index e7283d91..d24c667d 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "fix:lint": "madrun fix:lint", "lint:stream": "madrun lint:stream", "test": "madrun test", + "test:e2e": "madrun test:e2e", "test:client": "madrun test:client", "test:server": "madrun test:server", "wisdom": "madrun wisdom", diff --git a/server/server.js b/server/server.js index f1d0d405..06b6464a 100644 --- a/server/server.js +++ b/server/server.js @@ -20,8 +20,10 @@ const RATE_WINDOW = 15 * 60 * 1000; const bind = (f, self) => f.bind(self); const two = currify((f, a, b) => f(a, b)); -const shutdown = wraptile(async (promises) => { - console.log('closing cloudcmd...'); +const shutdown = wraptile(async (config, promises) => { + if (config('log')) + console.log('closing cloudcmd...'); + await Promise.all(promises); process.exit(0); }); @@ -41,7 +43,7 @@ export default async (options, config) => { const app = express(); const server = http.createServer(app); - if (logger) + if (config('log') && logger) app.use(logger('dev')); if (prefix) @@ -56,6 +58,8 @@ export default async (options, config) => { limit: RATE_LIMIT, }); + app.set('trust proxy', 1); + app.use(compression()); app.use(limiter); app.use(prefix, cloudcmd({ @@ -74,16 +78,17 @@ export default async (options, config) => { server.on('error', exitPort); await listen(port, ip); - const close = shutdown([closeServer, closeSocket]); + const close = shutdown(config, [closeServer, closeSocket]); process.on('SIGINT', close); - process.on('SIUSR1', close); + process.on('SIGUSR1', close); const host = config('ip') || 'localhost'; const port0 = port || server.address().port; const url = `http://${host}:${port0}${prefix}/`; - console.log(`url: ${url}`); + if (config('log')) + console.log(`url: ${url}`); if (!config('open')) return; diff --git a/test-e2e/ratelimit.js b/test-e2e/ratelimit.js new file mode 100644 index 00000000..632916c5 --- /dev/null +++ b/test-e2e/ratelimit.js @@ -0,0 +1,44 @@ +import process from 'node:process'; +import {test} from 'supertape'; + +let i = 0; + +test('cloudcmd: server: ratelimit: x-forwarded-for', async (t) => { + const PORT = 3000; + + process.env.PORT = PORT; + process.env.CLOUDCMD_LOG = 0; + + await import('../bin/cloudcmd.js'); + + const {status} = await fetch(`http://localhost:${PORT}`, { + headers: { + 'X-Forwarded-For': '127.0.0.1', + }, + }); + + process.kill(process.pid, 'SIGUSR1'); + + t.notEqual(status, 500); + t.end(); +}); + +test('cloudcmd: server: ratelimit', async (t) => { + const PORT = 3001; + const STATUS = 429; + + process.env.PORT = PORT; + process.env.CLOUDCMD_LOG = 0; + + await import(`../bin/cloudcmd.js?${i++}`); + + for (let i = 0; i < 1000; i++) { + await fetch(`http://localhost:${PORT}`); + } + + const {status} = await fetch(`http://localhost:${PORT}`); + process.kill(process.pid, 'SIGUSR1'); + + t.equal(status, STATUS); + t.end(); +}); diff --git a/test/e2e/ratelimit.js b/test/e2e/ratelimit.js deleted file mode 100644 index 75811aa1..00000000 --- a/test/e2e/ratelimit.js +++ /dev/null @@ -1,22 +0,0 @@ -import process from 'node:process'; -import {test} from 'supertape'; - -test('cloudcmd: server: ratelimit', async (t) => { - const PORT = 3000; - const STATUS = 429; - - process.env.PORT = PORT; - process.env.CLOUDCMD_LOG = 0; - - await import('../../bin/cloudcmd.js'); - - for (let i = 0; i < 1000; i++) { - await fetch(`http://localhost:${PORT}`); - } - - const {status} = await fetch(`http://localhost:${PORT}`); - process.kill(process.pid, 'SIGUSR1'); - - t.equal(status, STATUS); - t.end(); -});