@@ -9,14 +9,16 @@ final _log = Logger('AuthorizationMiddleware');
9
9
/// {@template authorization_middleware}
10
10
/// Middleware to enforce role-based permissions and model-specific access rules.
11
11
///
12
- /// This middleware reads the authenticated [User] , the requested `modelName` ,
13
- /// the `HttpMethod` , and the `ModelConfig` from the request context. It then
14
- /// determines the required permission based on the `ModelConfig` metadata for
15
- /// the specific HTTP method and checks if the authenticated user has that
16
- /// permission using the [PermissionService] .
12
+ /// This middleware reads the authenticated [User] (which may be null for
13
+ /// public routes), the requested `modelName` , the `HttpMethod` , and the
14
+ /// `ModelConfig` from the request context. It then determines the required
15
+ /// permission based on the `ModelConfig` metadata for the specific HTTP method
16
+ /// and checks if the authenticated user has that permission using the
17
+ /// [PermissionService] .
17
18
///
18
- /// If the user does not have the required permission, it throws a
19
- /// [ForbiddenException] , which should be caught by the 'errorHandler' middleware.
19
+ /// If the user does not have the required permission, or if authentication is
20
+ /// required but no user is present, it throws a [ForbiddenException] or
21
+ /// [UnauthorizedException] , which should be caught by the 'errorHandler' middleware.
20
22
///
21
23
/// This middleware runs *after* authentication and model validation.
22
24
/// It does NOT perform instance-level ownership checks; those are handled
@@ -27,8 +29,9 @@ Middleware authorizationMiddleware() {
27
29
return (handler) {
28
30
return (context) async {
29
31
// Read dependencies from the context.
30
- // User is guaranteed non-null by requireAuthentication() middleware.
31
- final user = context.read <User >();
32
+ // User is now read as nullable, as _conditionalAuthenticationMiddleware
33
+ // might provide null for public routes.
34
+ final user = context.read <User ?>();
32
35
final permissionService = context.read <PermissionService >();
33
36
final modelName = context.read <String >(); // Provided by data/_middleware
34
37
final modelConfig = context
@@ -64,23 +67,33 @@ Middleware authorizationMiddleware() {
64
67
);
65
68
}
66
69
67
- // Perform the permission check based on the configuration type
70
+ // Handle authentication requirement based on ModelConfig ---
71
+ if (requiredPermissionConfig.requiresAuthentication && user == null ) {
72
+ _log.warning (
73
+ 'Authorization required but no valid user found. Denying access.' ,
74
+ );
75
+ throw const UnauthorizedException ('Authentication required.' );
76
+ }
77
+
78
+ // If authentication is not required, or if user is present, proceed with permission checks.
79
+ // All subsequent checks must now handle a potentially null 'user' if 'requiresAuthentication' is false.
68
80
switch (requiredPermissionConfig.type) {
69
81
case RequiredPermissionType .none:
70
- // No specific permission required (beyond authentication if applicable)
71
- // This case is primarily for documentation/completeness if a route
72
- // group didn't require authentication, but the /data route does.
73
- // For the /data route, 'none' effectively means 'authenticated users allowed'.
82
+ // No specific permission required.
83
+ // If user is null, it's a public route. If user is not null, they are authenticated.
74
84
break ;
75
85
case RequiredPermissionType .adminOnly:
76
- // Requires the user to be an admin
77
- if (! permissionService.isAdmin (user)) {
86
+ // Requires the user to be an admin.
87
+ // If user is null here, it means requiresAuthentication was false,
88
+ // but adminOnly implies authentication. This is a misconfiguration
89
+ // or an attempt to access an admin-only resource publicly.
90
+ if (user == null || ! permissionService.isAdmin (user)) {
78
91
throw const ForbiddenException (
79
92
'Only administrators can perform this action.' ,
80
93
);
81
94
}
82
95
case RequiredPermissionType .specificPermission:
83
- // Requires a specific permission string
96
+ // Requires a specific permission string.
84
97
final permission = requiredPermissionConfig.permission;
85
98
if (permission == null ) {
86
99
// This indicates a configuration error in ModelRegistry
@@ -92,19 +105,21 @@ Middleware authorizationMiddleware() {
92
105
'Internal Server Error: Authorization configuration error.' ,
93
106
);
94
107
}
95
- if (! permissionService.hasPermission (user, permission)) {
108
+ // If user is null here, it means requiresAuthentication was false,
109
+ // but specificPermission implies authentication. This is a misconfiguration
110
+ // or an attempt to access a protected resource publicly.
111
+ if (user == null ||
112
+ ! permissionService.hasPermission (user, permission)) {
96
113
throw const ForbiddenException (
97
114
'You do not have permission to perform this action.' ,
98
115
);
99
116
}
100
117
case RequiredPermissionType .unsupported:
101
118
// This action is explicitly marked as not supported via this generic route.
102
- // Return Method Not Allowed.
103
119
_log.warning (
104
120
'Action for model "$modelName ", method "$method " is marked as '
105
121
'unsupported via generic route.' ,
106
122
);
107
- // Throw ForbiddenException to be caught by the errorHandler
108
123
throw ForbiddenException (
109
124
'Method "$method " is not supported for model "$modelName " '
110
125
'via this generic data endpoint.' ,
0 commit comments