Skip to content

Commit baec763

Browse files
committed
feat(api): implement rate limiting for data routes
- Add rate limiting middleware for /api/v1/data routes - Implement bypass permission for rate limiting - Configure rate limit using environment variables - Update middleware documentation and comments
1 parent 6134a81 commit baec763

File tree

1 file changed

+51
-13
lines changed

1 file changed

+51
-13
lines changed

routes/api/v1/data/_middleware.dart

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,60 @@
11
import 'package:core/core.dart';
22
import 'package:dart_frog/dart_frog.dart';
3+
import 'package:flutter_news_app_api_server_full_source_code/src/config/environment_config.dart';
34
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/authentication_middleware.dart';
4-
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/authorization_middleware.dart'; // Import authorization middleware
5+
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/authorization_middleware.dart';
6+
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/rate_limiter_middleware.dart';
7+
import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart';
8+
import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permissions.dart';
59
import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_registry.dart';
610

711
/// Middleware specific to the generic `/api/v1/data` route path.
812
///
913
/// This middleware chain performs the following in order:
1014
/// 1. **Authentication Check (`requireAuthentication`):** Ensures that the user
1115
/// is authenticated. If not, it aborts the request with a 401.
12-
/// 2. **Model Validation & Context Provision (`_modelValidationAndProviderMiddleware`):**
16+
/// 2. **Data Rate Limiting (`_dataRateLimiterMiddleware`):** Applies a
17+
/// configurable, user-centric rate limit. Bypassed by admin/publisher roles.
18+
/// 3. **Model Validation & Context Provision (`_modelValidationAndProviderMiddleware`):**
1319
/// - Validates the `model` query parameter.
1420
/// - Looks up the `ModelConfig` from the `ModelRegistryMap`.
15-
/// - Provides the `ModelConfig` and `modelName` into the request context
16-
/// for downstream middleware and route handlers.
17-
/// 3. **Authorization Check (`authorizationMiddleware`):** Enforces role-based
21+
/// - Provides the `ModelConfig` and `modelName` into the request context.
22+
/// 4. **Authorization Check (`authorizationMiddleware`):** Enforces role-based
1823
/// and model-specific permissions based on the `ModelConfig` metadata.
1924
/// If the user lacks permission, it throws a [ForbiddenException].
2025
///
2126
/// This setup ensures that data routes are protected, have the necessary
2227
/// model-specific configuration available, and access is authorized before
2328
/// reaching the final route handler.
2429
30+
// Helper middleware for applying rate limiting to the data routes.
31+
Middleware _dataRateLimiterMiddleware() {
32+
return (handler) {
33+
return (context) {
34+
final user = context.read<User>();
35+
final permissionService = context.read<PermissionService>();
36+
37+
// Users with the bypass permission are not rate-limited.
38+
if (permissionService.hasPermission(
39+
user,
40+
Permissions.rateLimitingBypass,
41+
)) {
42+
return handler(context);
43+
}
44+
45+
// For all other users, apply the configured rate limit.
46+
// The key is the user's ID, ensuring the limit is per-user.
47+
final rateLimitHandler = rateLimiter(
48+
limit: EnvironmentConfig.rateLimitDataApiLimit,
49+
window: EnvironmentConfig.rateLimitDataApiWindow,
50+
keyExtractor: (context) async => context.read<User>().id,
51+
)(handler);
52+
53+
return rateLimitHandler(context);
54+
};
55+
};
56+
}
57+
2558
// Helper middleware for model validation and context provision.
2659
Middleware _modelValidationAndProviderMiddleware() {
2760
return (handler) {
@@ -79,15 +112,19 @@ Handler middleware(Handler handler) {
79112
// resulting in a 401 response via the global `errorHandler`).
80113
// - If `User` is present, the request proceeds to the next middleware.
81114
//
82-
// 2. `_modelValidationAndProviderMiddleware()`:
115+
// 2. `_dataRateLimiterMiddleware()`:
83116
// - This runs if `requireAuthentication()` passes.
117+
// - It checks if the user has a bypass permission. If not, it applies
118+
// the configured rate limit based on the user's ID.
119+
// - If the limit is exceeded, it throws a `ForbiddenException`.
120+
//
121+
// 3. `_modelValidationAndProviderMiddleware()`:
122+
// - This runs if rate limiting passes.
84123
// - It validates the `?model=` query parameter and provides the
85124
// `ModelConfig` and `modelName` into the context.
86-
// - If model validation fails, it throws a BadRequestException, caught
87-
// by the global errorHandler.
88-
// - If successful, it calls the next handler in the chain (authorizationMiddleware).
125+
// - If model validation fails, it throws a `BadRequestException`.
89126
//
90-
// 3. `authorizationMiddleware()`:
127+
// 4. `authorizationMiddleware()`:
91128
// - This runs if `_modelValidationAndProviderMiddleware()` passes.
92129
// - It reads the `User`, `modelName`, and `ModelConfig` from the context.
93130
// - It checks if the user has permission to perform the requested HTTP
@@ -97,14 +134,15 @@ Handler middleware(Handler handler) {
97134
// - If successful, it calls the next handler in the chain (the actual
98135
// route handler).
99136
//
100-
// 4. Actual Route Handler (from `index.dart` or `[id].dart`):
137+
// 5. Actual Route Handler (from `index.dart` or `[id].dart`):
101138
// - This runs last, only if all preceding middlewares pass. It will have
102139
// access to a non-null `User`, `ModelConfig`, and `modelName` from the context.
103140
// - It performs the data operation and any necessary handler-level
104141
// ownership checks (if flagged by `ModelActionPermission.requiresOwnershipCheck`).
105142
//
106143
return handler
107-
.use(authorizationMiddleware()) // Applied third (inner)
108-
.use(_modelValidationAndProviderMiddleware()) // Applied second
144+
.use(authorizationMiddleware()) // Applied fourth (inner-most)
145+
.use(_modelValidationAndProviderMiddleware()) // Applied third
146+
.use(_dataRateLimiterMiddleware()) // Applied second
109147
.use(requireAuthentication()); // Applied first (outermost)
110148
}

0 commit comments

Comments
 (0)