From f670c024688fcb752e44b755f43bbe24acf35250 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 09:55:47 +0100 Subject: [PATCH 1/2] refactor(rbac): update role permissions for guest and standard users - Remove `Permissions.userUpdateOwned` from _appStandardUserPermissions - Add `Permissions.userUpdateOwned` to _appGuestUserPermissions - Include comments explaining the need for `Permissions.userUpdateOwned` --- lib/src/rbac/role_permissions.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/rbac/role_permissions.dart b/lib/src/rbac/role_permissions.dart index 973dcbf..ec892c1 100644 --- a/lib/src/rbac/role_permissions.dart +++ b/lib/src/rbac/role_permissions.dart @@ -14,12 +14,16 @@ final Set _appGuestUserPermissions = { Permissions.userContentPreferencesReadOwned, Permissions.userContentPreferencesUpdateOwned, Permissions.remoteConfigRead, + // Allows a user to update their own User object. This is essential for + // features like updating the `feedActionStatus` (e.g., when a user + // dismisses an in-feed prompt, etc). The endpoint handler ensures only + // non-sensitive fields can be modified. + Permissions.userUpdateOwned, }; final Set _appStandardUserPermissions = { ..._appGuestUserPermissions, Permissions.userReadOwned, - Permissions.userUpdateOwned, Permissions.userDeleteOwned, }; From 8c05e111b3096ed0f4b141910f4cc7eec23aa6df Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 8 Aug 2025 09:56:03 +0100 Subject: [PATCH 2/2] fix(api): implement safe update logic for user data endpoint - Prevent privilege escalation by not saving the entire request body - Perform a partial update of user data, allowing only safe fields to be modified - Protect critical fields: appRole, dashboardRole, id, createdAt, email - Allow update of feedActionStatus as it's considered safe user interaction data - Future-proof the logic for potential safe fields addition --- routes/api/v1/data/[id]/index.dart | 38 +++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/routes/api/v1/data/[id]/index.dart b/routes/api/v1/data/[id]/index.dart index 96b9ed0..d063d36 100644 --- a/routes/api/v1/data/[id]/index.dart +++ b/routes/api/v1/data/[id]/index.dart @@ -324,9 +324,45 @@ Future _handlePut( case 'user': { final repo = context.read>(); + + // --- Safe User Update Logic --- + // To prevent security vulnerabilities like privilege escalation, we do not + // simply save the entire request body. Instead, we perform a safe, + // partial update. + + // 1. Fetch the existing, trusted user object from the database. + // This ensures we have the current, authoritative state of the user, + // including their correct roles and ID. + final existingUser = await repo.read( + id: id, + userId: userIdForRepoCall, + ); + + // 2. Create a new User object by merging only the allowed, safe-to-update + // fields from the incoming request (`itemToUpdate`) into the + // existing user data. + // This is the most critical step. It guarantees that a user cannot + // change their own `appRole`, `dashboardRole`, `id`, or `createdAt` + // fields, even if they include them in the request payload. + // The `email` field is also protected here, as changing it requires a + // separate, secure verification flow (e.g., via a dedicated endpoint) + // and should not be done through this generic data endpoint. + final updatedUser = existingUser.copyWith( + // `feedActionStatus` is considered safe for a user to update as it + // only tracks their interaction with UI elements. + feedActionStatus: (itemToUpdate as User).feedActionStatus, + + // FUTURE: If a `displayName` field were added to the User model, + // it would also be considered safe and could be updated here: + // displayName: itemToUpdate.displayName, + ); + + // 3. Save the securely merged user object back to the database. + // The repository will now update the user record with our safely + // constructed `updatedUser` object. updatedItem = await repo.update( id: id, - item: itemToUpdate as User, + item: updatedUser, userId: userIdForRepoCall, ); }