Description
The current rate limiting on POST /gists is IP-based only via ThrottlerGuard. Since GistPin is a Web3 app where users identify with Stellar addresses, we need a second rate limiting layer that caps how many gists a single Stellar address can post per hour — regardless of IP address.
Context
- Current: ThrottlerGuard limits by IP at 10 requests per minute
- Needed: additional limit per authorAddress — 20 gists per hour per Stellar address
- Anonymous posts with no authorAddress are still limited by IP only
- Use an in-memory Map for tracking — no Redis required
Requirements
- Create a custom StellarAddressThrottlerGuard implementing CanActivate
- Extract authorAddress from the request body
- Track post count per address using an in-memory Map
- Reset counters every hour using Date.now()
- Skip throttling if authorAddress is not present — anonymous posts pass through
- Return 429 with message: Rate limit exceeded for this Stellar address
- Make the limit configurable via STELLAR_RATE_LIMIT_PER_HOUR env var, default 20
- Log when rate limit is hit — log a hash of the address, not the full address
Files to Touch
- Backend/src/common/guards/stellar-throttler.guard.ts — create custom guard
- Backend/src/gists/gists.controller.ts — apply @UseGuards on the POST route
- Backend/src/config/configuration.ts — add STELLAR_RATE_LIMIT_PER_HOUR
- Backend/.env.example — add STELLAR_RATE_LIMIT_PER_HOUR=20
Guard Structure Reference
@Injectable()
export class StellarAddressThrottlerGuard implements CanActivate {
private readonly counts = new Map<string, { count: number; resetAt: number }>();
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const address = request.body?.authorAddress;
if (!address) return true;
const limit = parseInt(process.env.STELLAR_RATE_LIMIT_PER_HOUR ?? '20');
const now = Date.now();
const entry = this.counts.get(address);
if (!entry || now > entry.resetAt) {
this.counts.set(address, { count: 1, resetAt: now + 3_600_000 });
return true;
}
if (entry.count >= limit) return false;
entry.count++;
return true;
}
}
Acceptance Criteria
Complexity: 250 points
Description
The current rate limiting on POST /gists is IP-based only via ThrottlerGuard. Since GistPin is a Web3 app where users identify with Stellar addresses, we need a second rate limiting layer that caps how many gists a single Stellar address can post per hour — regardless of IP address.
Context
Requirements
Files to Touch
Guard Structure Reference
Acceptance Criteria
Complexity: 250 points