1
1
import 'package:core/core.dart' ;
2
2
import 'package:dart_frog/dart_frog.dart' ;
3
- import 'package:data_repository/data_repository.dart' ;
4
3
import 'package:flutter_news_app_api_server_full_source_code/src/rbac/permission_service.dart' ;
5
4
import 'package:flutter_news_app_api_server_full_source_code/src/registry/model_registry.dart' ;
6
5
@@ -19,28 +18,27 @@ class FetchedItem<T> {
19
18
/// Middleware to check if the authenticated user is the owner of the requested
20
19
/// item.
21
20
///
22
- /// This middleware is designed to run on item-specific routes (e.g., `/[id]` ).
23
- /// It performs the following steps:
21
+ /// This middleware runs *after* the `dataFetchMiddleware` , which means it can
22
+ /// safely assume that the requested item has already been fetched and is
23
+ /// available in the context.
24
24
///
25
- /// 1. Determines if an ownership check is required for the current action
26
- /// (GET, PUT, DELETE) based on the `ModelConfig`.
27
- /// 2. If a check is required and the user is not an admin, it fetches the
28
- /// item from the database.
29
- /// 3. It then compares the item's owner ID with the authenticated user's ID.
30
- /// 4. If the check fails, it throws a [ForbiddenException] .
31
- /// 5. If the check passes, it provides the fetched item into the request
32
- /// context via `context.provide<FetchedItem<dynamic>>`. This prevents the
33
- /// downstream route handler from needing to fetch the item again.
25
+ /// It performs the following steps:
26
+ /// 1. Determines if an ownership check is required for the current action
27
+ /// based on the `ModelConfig`.
28
+ /// 2. If a check is required and the user is not an admin, it reads the
29
+ /// pre-fetched item from the context.
30
+ /// 3. It then compares the item's owner ID with the authenticated user's ID.
31
+ /// 4. If the IDs do not match, it throws a [ForbiddenException] .
32
+ /// 5. If the check is not required or passes, it calls the next handler.
34
33
Middleware ownershipCheckMiddleware () {
35
34
return (handler) {
36
35
return (context) async {
37
- final modelName = context.read <String >();
38
36
final modelConfig = context.read <ModelConfig <dynamic >>();
39
37
final user = context.read <User >();
40
38
final permissionService = context.read <PermissionService >();
41
39
final method = context.request.method;
42
- final id = context.request.uri.pathSegments.last;
43
40
41
+ // Determine the required permission configuration for the current method.
44
42
ModelActionPermission permission;
45
43
switch (method) {
46
44
case HttpMethod .get :
@@ -50,54 +48,41 @@ Middleware ownershipCheckMiddleware() {
50
48
case HttpMethod .delete:
51
49
permission = modelConfig.deletePermission;
52
50
default :
53
- // For other methods, no ownership check is performed here .
51
+ // For any other methods, no ownership check is performed.
54
52
return handler (context);
55
53
}
56
54
57
- // If no ownership check is required or if the user is an admin,
58
- // proceed to the next handler without fetching the item .
55
+ // If no ownership check is required for this action, or if the user is
56
+ // an admin (who bypasses ownership checks), proceed immediately .
59
57
if (! permission.requiresOwnershipCheck ||
60
58
permissionService.isAdmin (user)) {
61
59
return handler (context);
62
60
}
63
61
62
+ // At this point, an ownership check is required for a non-admin user.
63
+
64
+ // Ensure the model is configured to support ownership checks.
64
65
if (modelConfig.getOwnerId == null ) {
65
66
throw const OperationFailedException (
66
67
'Internal Server Error: Model configuration error for ownership check.' ,
67
68
);
68
69
}
69
70
70
- final userIdForRepoCall = user.id;
71
- dynamic item;
72
-
73
- switch (modelName) {
74
- case 'user' :
75
- final repo = context.read <DataRepository <User >>();
76
- item = await repo.read (id: id, userId: userIdForRepoCall);
77
- case 'user_app_settings' :
78
- final repo = context.read <DataRepository <UserAppSettings >>();
79
- item = await repo.read (id: id, userId: userIdForRepoCall);
80
- case 'user_content_preferences' :
81
- final repo = context.read <DataRepository <UserContentPreferences >>();
82
- item = await repo.read (id: id, userId: userIdForRepoCall);
83
- default :
84
- throw OperationFailedException (
85
- 'Ownership check not implemented for model "$modelName ".' ,
86
- );
87
- }
71
+ // Read the item that was pre-fetched by the dataFetchMiddleware.
72
+ // This is guaranteed to exist because dataFetchMiddleware would have
73
+ // thrown a NotFoundException if the item did not exist.
74
+ final item = context.read <FetchedItem <dynamic >>().data;
88
75
76
+ // Compare the item's owner ID with the authenticated user's ID.
89
77
final itemOwnerId = modelConfig.getOwnerId !(item);
90
78
if (itemOwnerId != user.id) {
91
79
throw const ForbiddenException (
92
80
'You do not have permission to access this item.' ,
93
81
);
94
82
}
95
83
96
- final updatedContext = context.provide <FetchedItem <dynamic >>(
97
- () => FetchedItem (item),
98
- );
99
-
100
- return handler (updatedContext);
84
+ // If the ownership check passes, proceed to the final route handler.
85
+ return handler (context);
101
86
};
102
87
};
103
88
}
0 commit comments