Skip to content

Commit 14ba5d4

Browse files
committed
feat(data): implement conditional authentication middleware
- Add _conditionalAuthenticationMiddleware to check ModelConfig for authentication requirements - Update middleware stack to use _conditionalAuthenticationMiddleware instead of requireAuthentication - Improve code documentation for data route middlewares
1 parent 543df42 commit 14ba5d4

File tree

1 file changed

+61
-12
lines changed

1 file changed

+61
-12
lines changed

routes/api/v1/data/_middleware.dart

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Middleware _dataRateLimiterMiddleware() {
3636
};
3737
}
3838

39-
// Helper middleware for model validation and context provision.
39+
/// Helper middleware for model validation and context provision.
4040
Middleware _modelValidationAndProviderMiddleware() {
4141
return (handler) {
4242
// This 'handler' is the next handler in the chain,
@@ -76,6 +76,56 @@ Middleware _modelValidationAndProviderMiddleware() {
7676
};
7777
}
7878

79+
/// Helper middleware to conditionally apply authentication based on
80+
/// `ModelConfig`.
81+
///
82+
/// This middleware checks the `requiresAuthentication` flag on the
83+
/// `ModelActionPermission` for the current model and HTTP method.
84+
/// If authentication is required, it calls `requireAuthentication()`.
85+
/// If not, it simply passes the request through, allowing public access.
86+
Middleware _conditionalAuthenticationMiddleware() {
87+
return (handler) {
88+
return (context) {
89+
final modelConfig = context.read<ModelConfig<dynamic>>();
90+
final method = context.request.method;
91+
92+
ModelActionPermission requiredPermissionConfig;
93+
switch (method) {
94+
case HttpMethod.get:
95+
// Differentiate GET based on whether it's a collection or item request
96+
final isCollectionRequest =
97+
context.request.uri.path == '/api/v1/data';
98+
if (isCollectionRequest) {
99+
requiredPermissionConfig = modelConfig.getCollectionPermission;
100+
} else {
101+
requiredPermissionConfig = modelConfig.getItemPermission;
102+
}
103+
case HttpMethod.post:
104+
requiredPermissionConfig = modelConfig.postPermission;
105+
case HttpMethod.put:
106+
requiredPermissionConfig = modelConfig.putPermission;
107+
case HttpMethod.delete:
108+
requiredPermissionConfig = modelConfig.deletePermission;
109+
default:
110+
// For unsupported methods, assume authentication is required
111+
// or let subsequent middleware/route handler deal with it.
112+
requiredPermissionConfig = const ModelActionPermission(
113+
type: RequiredPermissionType.unsupported,
114+
requiresAuthentication: true,
115+
);
116+
}
117+
118+
if (requiredPermissionConfig.requiresAuthentication) {
119+
// If authentication is required, apply the requireAuthentication middleware.
120+
return requireAuthentication()(handler)(context);
121+
} else {
122+
// If authentication is not required, simply pass the request through.
123+
return handler(context);
124+
}
125+
};
126+
};
127+
}
128+
79129
// Main middleware exported for the /api/v1/data route group.
80130
Handler middleware(Handler handler) {
81131
// This 'handler' is the actual route handler from index.dart or [id].dart.
@@ -84,17 +134,15 @@ Handler middleware(Handler handler) {
84134
// the last .use() call in the chain represents the outermost middleware layer.
85135
// Therefore, the execution order for an incoming request is:
86136
//
87-
// 1. `requireAuthentication()`:
88-
// - This runs first. It relies on `authenticationProvider()` (from the
89-
// parent `/api/v1/_middleware.dart`) having already attempted to
90-
// authenticate the user and provide `User?` into the context.
91-
// - If `User` is null (no valid authentication), `requireAuthentication()`
92-
// throws an `UnauthorizedException`, and the request is aborted (usually
93-
// resulting in a 401 response via the global `errorHandler`).
94-
// - If `User` is present, the request proceeds to the next middleware.
137+
// 1. `_conditionalAuthenticationMiddleware()`:
138+
// - This runs first. It dynamically decides whether to apply
139+
// `requireAuthentication()` based on the `ModelConfig` for the
140+
// requested model and HTTP method.
141+
// - If authentication is required and the user is not authenticated,
142+
// it throws an `UnauthorizedException`.
95143
//
96144
// 2. `_dataRateLimiterMiddleware()`:
97-
// - This runs if `requireAuthentication()` passes.
145+
// - This runs if authentication (if required) passes.
98146
// - It checks if the user has a bypass permission. If not, it applies
99147
// the configured rate limit based on the user's ID.
100148
// - If the limit is exceeded, it throws a `ForbiddenException`.
@@ -117,13 +165,14 @@ Handler middleware(Handler handler) {
117165
//
118166
// 5. Actual Route Handler (from `index.dart` or `[id].dart`):
119167
// - This runs last, only if all preceding middlewares pass. It will have
120-
// access to a non-null `User`, `ModelConfig`, and `modelName` from the context.
168+
// access to a non-null `User` (if authenticated), `ModelConfig`, and
169+
// `modelName` from the context.
121170
// - It performs the data operation and any necessary handler-level
122171
// ownership checks (if flagged by `ModelActionPermission.requiresOwnershipCheck`).
123172
//
124173
return handler
125174
.use(authorizationMiddleware()) // Applied fourth (inner-most)
126175
.use(_modelValidationAndProviderMiddleware()) // Applied third
127176
.use(_dataRateLimiterMiddleware()) // Applied second
128-
.use(requireAuthentication()); // Applied first (outermost)
177+
.use(_conditionalAuthenticationMiddleware()); // Applied first (outermost)
129178
}

0 commit comments

Comments
 (0)