feat(sync): rate limit sync/backfill endpoints to 10 per 15 minutes

This commit is contained in:
null 2026-06-06 15:08:33 -05:00
parent 7d42d119c0
commit 80b5d56010
2 changed files with 18 additions and 3 deletions

View File

@ -63,6 +63,18 @@ const demoDataLimiter = makeLimiter(
'Too many demo data clear operations. Please try again in 15 minutes.',
);
// 10 sync/backfill requests per 15 minutes per user — prevents SimpleFIN hammering
const syncLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
standardHeaders: 'draft-7',
legacyHeaders: false,
keyGenerator: (req) => req.user?.id?.toString() || req.ip,
handler(req, res) {
res.status(429).json({ error: 'Too many sync requests. Please try again in 15 minutes.' });
},
});
// ── Export all limiters plus reset function ────────────────────────────────────
const allLimiters = [
loginLimiter,
@ -73,6 +85,7 @@ const allLimiters = [
oidcLimiter,
backupOperationLimiter,
demoDataLimiter,
syncLimiter,
];
function resetStores() {
@ -92,5 +105,6 @@ module.exports = {
oidcLimiter,
backupOperationLimiter,
demoDataLimiter,
syncLimiter,
resetStores,
};

View File

@ -7,6 +7,7 @@ const { decorateDataSource, ensureManualDataSource } = require('../services/tran
const { connectSimplefin, syncDataSource, backfillDataSource, disconnectDataSource } = require('../services/bankSyncService');
const { sanitizeErrorMessage } = require('../services/simplefinService');
const { getBankSyncConfig } = require('../services/bankSyncConfigService');
const { syncLimiter } = require('../middleware/rateLimiter');
const VALID_TYPES = new Set(['manual', 'file_import', 'provider_sync']);
const VALID_STATUSES = new Set(['active', 'inactive', 'error']);
@ -207,7 +208,7 @@ router.put('/:sourceId/accounts/:accountId', (req, res) => {
// ─── POST /api/data-sources/:id/sync ─────────────────────────────────────────
router.post('/:id/sync', async (req, res) => {
router.post('/:id/sync', syncLimiter, async (req, res) => {
if (!getBankSyncConfig().enabled) {
return res.status(503).json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
}
@ -230,7 +231,7 @@ router.post('/:id/sync', async (req, res) => {
// ─── POST /api/data-sources/sync-all ─────────────────────────────────────────
// Syncs every SimpleFIN source for the current user. Returns aggregated stats.
router.post('/sync-all', async (req, res) => {
router.post('/sync-all', syncLimiter, async (req, res) => {
if (!getBankSyncConfig().enabled) {
return res.status(503).json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
}
@ -284,7 +285,7 @@ router.post('/sync-all', async (req, res) => {
// ─── POST /api/data-sources/:id/backfill ─────────────────────────────────────
router.post('/:id/backfill', async (req, res) => {
router.post('/:id/backfill', syncLimiter, async (req, res) => {
if (!getBankSyncConfig().enabled) {
return res.status(503).json(standardizeError('Bank sync is not enabled on this server', 'BANK_SYNC_DISABLED'));
}