feature: cloudcmd: rate limit: add support (#437)

This commit is contained in:
coderiaser 2026-05-26 00:17:44 +03:00
parent 161bede8db
commit 5d9628ce70
5 changed files with 38 additions and 20 deletions

View file

@ -115,7 +115,7 @@ const yargsOptions = {
'columns': env.parse('columns') || config('columns') || '',
'theme': env.parse('theme') || config('theme') || '',
'vim': choose(env.bool('vim'), config('vim')),
'log': config('log'),
'log': choose(env.bool('log'), config('log')),
'import-url': env.parse('import_url') || config('importUrl'),
'import-listen': choose(env.bool('import_listen'), config('importListen')),

View file

@ -105,6 +105,7 @@
"es6-promisify": "^7.0.0",
"execon": "^1.2.0",
"express": "^5.1.0",
"express-rate-limit": "^8.5.2",
"files-io": "^4.0.0",
"find-up": "^8.0.0",
"for-each-key": "^2.0.0",

View file

@ -178,20 +178,3 @@ test('cloudcmd: sw', async (t) => {
t.equal(status, 200, 'should return sw');
t.end();
});
test('cloudcmd: manifest.json', async (t) => {
const config = {
auth: true,
};
const options = {
config,
};
const {status} = await request.get('/public/manifest.json', {
options,
});
t.equal(status, 200, 'should return manifest.json even when authentication is enabled');
t.end();
});

View file

@ -1,18 +1,22 @@
import http from 'node:http';
import {promisify} from 'node:util';
import process from 'node:process';
import {rateLimit} from 'express-rate-limit';
import currify from 'currify';
import squad from 'squad';
import {tryToCatch} from 'try-to-catch';
import opn from 'open';
import express from 'express';
import {Server} from 'socket.io';
import tryRequire from 'tryrequire';
import wraptile from 'wraptile';
import compression from 'compression';
import tryRequire from 'tryrequire';
import {cloudcmd} from '#server/cloudcmd';
import exit from './exit.js';
const RATE_LIMIT = 1000;
const RATE_WINDOW = 15 * 60 * 1000;
const bind = (f, self) => f.bind(self);
const two = currify((f, a, b) => f(a, b));
@ -25,6 +29,7 @@ const shutdown = wraptile(async (promises) => {
const promisifySelf = squad(promisify, bind);
const exitPort = two(exit, 'cloudcmd --port: %s');
const logger = tryRequire('morgan');
export default async (options, config) => {
@ -46,8 +51,13 @@ export default async (options, config) => {
path: `${prefix}/socket.io`,
});
app.use(compression());
const limiter = rateLimit({
windowMs: RATE_WINDOW,
limit: RATE_LIMIT,
});
app.use(compression());
app.use(limiter);
app.use(prefix, cloudcmd({
config: options,
socket: socketServer,
@ -65,7 +75,9 @@ export default async (options, config) => {
await listen(port, ip);
const close = shutdown([closeServer, closeSocket]);
process.on('SIGINT', close);
process.on('SIUSR1', close);
const host = config('ip') || 'localhost';
const port0 = port || server.address().port;

22
test/e2e/ratelimit.js Normal file
View file

@ -0,0 +1,22 @@
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();
});