Skip to content

Commit d2c8e7e

Browse files
committed
feat(auth): add rate limiting to request-code endpoint
- Implement rate limiting middleware for the /request-code endpoint - Allow up to 3 requests per IP address every 24 hours - Use ipKeyExtractor for rate limiting key generation - Refactor handler logic to include rate limiting
1 parent c889ba4 commit d2c8e7e

File tree

1 file changed

+49
-35
lines changed

1 file changed

+49
-35
lines changed

routes/api/v1/auth/request-code.dart

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,14 @@ import 'dart:io';
22

33
import 'package:core/core.dart'; // For exceptions
44
import 'package:dart_frog/dart_frog.dart';
5+
import 'package:flutter_news_app_api_server_full_source_code/src/middlewares/rate_limiter_middleware.dart';
56
import 'package:flutter_news_app_api_server_full_source_code/src/services/auth_service.dart';
67
import 'package:logging/logging.dart';
78

89
// Create a logger for this file.
910
final _logger = Logger('request_code_handler');
1011

11-
/// Handles POST requests to `/api/v1/auth/request-code`.
12-
///
13-
/// Initiates an email-based sign-in process. This endpoint is context-aware.
14-
///
15-
/// - For the user-facing app, it sends a verification code to the provided
16-
/// email, supporting both sign-in and sign-up.
17-
/// - For the dashboard, the request body must include `"isDashboardLogin": true`.
18-
/// In this mode, it first verifies the user exists and has 'admin' or
19-
/// 'publisher' roles before sending a code, effectively acting as a
20-
/// login-only gate.
21-
Future<Response> onRequest(RequestContext context) async {
22-
// Ensure this is a POST request
23-
if (context.request.method != HttpMethod.post) {
24-
return Response(statusCode: HttpStatus.methodNotAllowed);
25-
}
26-
12+
Future<Response> _onRequest(RequestContext context) async {
2713
// Read the AuthService provided by middleware
2814
final authService = context.read<AuthService>();
2915

@@ -70,25 +56,53 @@ Future<Response> onRequest(RequestContext context) async {
7056
}
7157

7258
try {
73-
// Call the AuthService to handle the logic, passing the context flag.
74-
await authService.initiateEmailSignIn(
75-
email,
76-
isDashboardLogin: isDashboardLogin,
77-
);
59+
// Call the AuthService to handle the logic, passing the context flag.
60+
await authService.initiateEmailSignIn(
61+
email,
62+
isDashboardLogin: isDashboardLogin,
63+
);
7864

79-
// Return 202 Accepted: The request is accepted for processing,
80-
// but the processing (email sending) hasn't necessarily completed.
81-
// 200 OK is also acceptable if you consider the API call itself complete.
82-
return Response(statusCode: HttpStatus.accepted);
83-
} on HttpException catch (_) {
84-
// Let the central errorHandler middleware handle known exceptions
85-
rethrow;
86-
} catch (e, s) {
87-
// Catch unexpected errors from the service layer
88-
_logger.severe('Unexpected error in /request-code handler', e, s);
89-
// Let the central errorHandler handle this as a 500
90-
throw const OperationFailedException(
91-
'An unexpected error occurred while requesting the sign-in code.',
92-
);
65+
// Return 202 Accepted: The request is accepted for processing,
66+
// but the processing (email sending) hasn't necessarily completed.
67+
// 200 OK is also acceptable if you consider the API call itself complete.
68+
return Response(statusCode: HttpStatus.accepted);
69+
} on HttpException catch (_) {
70+
// Let the central errorHandler middleware handle known exceptions
71+
rethrow;
72+
} catch (e, s) {
73+
// Catch unexpected errors from the service layer
74+
_logger.severe('Unexpected error in /request-code handler', e, s);
75+
// Let the central errorHandler handle this as a 500
76+
throw const OperationFailedException(
77+
'An unexpected error occurred while requesting the sign-in code.',
78+
);
79+
}
80+
}
81+
82+
/// Handles POST requests to `/api/v1/auth/request-code`.
83+
///
84+
/// Initiates an email-based sign-in process. This endpoint is context-aware.
85+
///
86+
/// - For the user-facing app, it sends a verification code to the provided
87+
/// email, supporting both sign-in and sign-up.
88+
/// - For the dashboard, the request body must include `"isDashboardLogin": true`.
89+
/// In this mode, it first verifies the user exists and has 'admin' or
90+
/// 'publisher' roles before sending a code, effectively acting as a
91+
/// login-only gate.
92+
Future<Response> onRequest(RequestContext context) async {
93+
// Ensure this is a POST request
94+
if (context.request.method != HttpMethod.post) {
95+
return Response(statusCode: HttpStatus.methodNotAllowed);
9396
}
97+
98+
// Apply the rate limiter middleware before calling the actual handler.
99+
final handler = const Pipeline().addMiddleware(
100+
rateLimiter(
101+
limit: 3,
102+
window: const Duration(hours: 24),
103+
keyExtractor: ipKeyExtractor,
104+
),
105+
).addHandler(_onRequest);
106+
107+
return handler(context);
94108
}

0 commit comments

Comments
 (0)