Skip to content

Commit b2b4363

Browse files
committed
fix(api): correct admin data scoping in generic data handlers
Fixes a bug where administrators were incorrectly scoped to their own userId when accessing user-owned resources, preventing them from managing other users' data. The logic in the generic data handlers (`/data` and `/data/[id]`) has been updated to only apply the `userId` filter to repository calls if the model is user-owned AND the authenticated user is not an admin. This allows administrators to perform global operations as intended.
1 parent c49d2c4 commit b2b4363

File tree

2 files changed

+15
-7
lines changed

2 files changed

+15
-7
lines changed

routes/api/v1/data/[id]/index.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ Future<Response> _handleGet(
8787
// for filtering. Otherwise, pass null.
8888
// Note: This is for data *scoping* by the repository, not the permission check.
8989
// We infer user-owned based on the presence of getOwnerId function.
90-
if (modelConfig.getOwnerId != null) {
90+
if (modelConfig.getOwnerId != null &&
91+
!permissionService.isAdmin(authenticatedUser)) {
9192
userIdForRepoCall = authenticatedUser.id;
9293
} else {
9394
userIdForRepoCall = null;
@@ -276,7 +277,8 @@ Future<Response> _handlePut(
276277
String? userIdForRepoCall;
277278
// If the model is user-owned, pass the authenticated user's ID to the repository
278279
// for ownership enforcement. Otherwise, pass null.
279-
if (modelConfig.getOwnerId != null) {
280+
if (modelConfig.getOwnerId != null &&
281+
!permissionService.isAdmin(authenticatedUser)) {
280282
userIdForRepoCall = authenticatedUser.id;
281283
} else {
282284
userIdForRepoCall = null;
@@ -445,7 +447,8 @@ Future<Response> _handleDelete(
445447
String? userIdForRepoCall;
446448
// If the model is user-owned, pass the authenticated user's ID to the repository
447449
// for ownership enforcement. Otherwise, pass null.
448-
if (modelConfig.getOwnerId != null) {
450+
if (modelConfig.getOwnerId != null &&
451+
!permissionService.isAdmin(authenticatedUser)) {
449452
userIdForRepoCall = authenticatedUser.id;
450453
} else {
451454
userIdForRepoCall = null;

routes/api/v1/data/index.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:io';
44
import 'package:dart_frog/dart_frog.dart';
55
import 'package:ht_api/src/registry/model_registry.dart';
66
import 'package:ht_data_repository/ht_data_repository.dart';
7+
import 'package:ht_api/src/rbac/permission_service.dart';
78
import 'package:ht_shared/ht_shared.dart';
89

910
import '../../../_middleware.dart'; // For RequestId
@@ -77,8 +78,10 @@ Future<Response> _handleGet(RequestContext context) async {
7778
}
7879

7980
// --- Repository Call ---
80-
final userIdForRepoCall =
81-
(modelConfig.getOwnerId != null) ? authenticatedUser.id : null;
81+
final userIdForRepoCall = (modelConfig.getOwnerId != null &&
82+
!context.read<PermissionService>().isAdmin(authenticatedUser))
83+
? authenticatedUser.id
84+
: null;
8285

8386
dynamic responseData;
8487

@@ -175,8 +178,10 @@ Future<Response> _handlePost(RequestContext context) async {
175178
}
176179

177180
// --- Repository Call ---
178-
final userIdForRepoCall =
179-
(modelConfig.getOwnerId != null) ? authenticatedUser.id : null;
181+
final userIdForRepoCall = (modelConfig.getOwnerId != null &&
182+
!context.read<PermissionService>().isAdmin(authenticatedUser))
183+
? authenticatedUser.id
184+
: null;
180185

181186
dynamic createdItem;
182187
switch (modelName) {

0 commit comments

Comments
 (0)