Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions stellar-payment-platform/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ const limiter = rateLimit({
},
});

// Per-IP limiter specifically for sensitive, unauthenticated endpoints.
// Keys strictly by client IP so brute-force/spam from a single source is
// blocked regardless of how many account ids are rotated in the payload.
const ipLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
store: redisClient ? new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}) : undefined,
standardHeaders: true,
legacyHeaders: true,
message: errorBody('RATE_LIMITED', 'Too many requests, please try again later.'),
keyGenerator: (req) => req.ip || (req.connection && req.connection.remoteAddress) || '',
});

app.use(cors(corsOptions));
app.use(express.json());

Expand Down Expand Up @@ -383,7 +398,7 @@ app.get('/metrics', async (req, res) => {
}
});

app.get('/federation', etagCache, validateSchema({ query: federationQuerySchema }), async (req, res, next) => {
app.get('/federation', ipLimiter, etagCache, validateSchema({ query: federationQuerySchema }), async (req, res, next) => {
const { q: queryValue, type } = req.query;

try {
Expand Down Expand Up @@ -564,7 +579,7 @@ const verifyFreighterRegistrationSignature = ({
* - Validates that provided signature(s) meet minimum threshold
* - Ensures authorization requirements are satisfied
*/
app.post('/register', idempotencyMiddleware(redisClient), requireJson, validateSchema({ body: registerBodySchema }), async (req, res, next) => {
app.post('/register', ipLimiter, idempotencyMiddleware(redisClient), requireJson, validateSchema({ body: registerBodySchema }), async (req, res, next) => {
// registerBodySchema has already guaranteed that username is a trimmed
// 3-20 character alphanumeric string and address is a non-empty trimmed
// string, so those shape checks are not repeated here.
Expand Down
Loading