feat(sync): rate limit sync/backfill endpoints to 10 per 15 minutes
This commit is contained in:
parent
7d42d119c0
commit
80b5d56010
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue