|
| 1 | +import 'package:dart_frog/dart_frog.dart'; |
| 2 | +import 'package:flutter_news_app_api_server_full_source_code/src/services/rate_limit_service.dart'; |
| 3 | + |
| 4 | +/// Extracts the client's IP address from the request. |
| 5 | +/// |
| 6 | +/// It prioritizes the 'X-Forwarded-For' header, which is standard for |
| 7 | +/// identifying the originating IP of a client connecting through a proxy |
| 8 | +/// or load balancer. If that header is not present, it falls back to the |
| 9 | +/// direct connection's IP address. |
| 10 | +String? _getIpAddress(RequestContext context) { |
| 11 | + final headers = context.request.headers; |
| 12 | + // The 'X-Forwarded-For' header can contain a comma-separated list of IPs. |
| 13 | + // The first one is typically the original client IP. |
| 14 | + final xff = headers['X-Forwarded-For']?.split(',').first.trim(); |
| 15 | + if (xff != null && xff.isNotEmpty) { |
| 16 | + return xff; |
| 17 | + } |
| 18 | + // Fallback to the direct connection IP if XFF is not available. |
| 19 | + return context.request.connectionInfo?.remoteAddress.address; |
| 20 | +} |
| 21 | + |
| 22 | +/// Middleware to enforce rate limiting on a route. |
| 23 | +/// |
| 24 | +/// This middleware uses the [RateLimitService] to track and limit the number |
| 25 | +/// of requests from a unique source (identified by a key) within a specific |
| 26 | +/// time window. |
| 27 | +/// |
| 28 | +/// - [limit]: The maximum number of requests allowed. |
| 29 | +/// - [window]: The time duration in which the requests are counted. |
| 30 | +/// - [keyExtractor]: A function that extracts a unique key from the request |
| 31 | +/// context. This could be an IP address, an email from the body, etc. |
| 32 | +Middleware rateLimiter({ |
| 33 | + required int limit, |
| 34 | + required Duration window, |
| 35 | + required Future<String?> Function(RequestContext) keyExtractor, |
| 36 | +}) { |
| 37 | + return (handler) { |
| 38 | + return (context) async { |
| 39 | + final rateLimitService = context.read<RateLimitService>(); |
| 40 | + final key = await keyExtractor(context); |
| 41 | + |
| 42 | + // If a key cannot be extracted, we bypass the rate limiter. |
| 43 | + // This is a safeguard; for IP-based limiting, a key should always exist. |
| 44 | + if (key == null || key.isEmpty) { |
| 45 | + return handler(context); |
| 46 | + } |
| 47 | + |
| 48 | + // The checkRequest method will throw a ForbiddenException if the |
| 49 | + // limit is exceeded. This will be caught by the global error handler. |
| 50 | + await rateLimitService.checkRequest( |
| 51 | + key: key, |
| 52 | + limit: limit, |
| 53 | + window: window, |
| 54 | + ); |
| 55 | + |
| 56 | + // If the check passes, proceed to the next handler. |
| 57 | + return handler(context); |
| 58 | + }; |
| 59 | + }; |
| 60 | +} |
| 61 | + |
| 62 | +/// A specific implementation of the [keyExtractor] for IP-based rate limiting. |
| 63 | +Future<String?> ipKeyExtractor(RequestContext context) async { |
| 64 | + return _getIpAddress(context); |
| 65 | +} |
0 commit comments