Return forbidden from the admin-or-self check on crowdin_username

What does this MR do and why?

  • Two "admin or self" authorization checks disagreed on their failure response. Api::V1::UsersController#crowdin_username returned 401 Unauthorized, while Api::V1::Users::MentorsController#ensure_admin_or_owner! returned 403 Forbidden for the equivalent condition. This MR aligns the first onto 403.
  • 403 is the correct status here. Both checks run after ensure_logged_in, so the caller is always authenticated by the time either is reached. 401 means the request lacked valid credentials, which is never the case at that point the caller is known, they simply are not the owner or an admin.
  • This is also the convention the codebase already sets. ApiApplicationController draws exactly this line: ensure_logged_in returns 401, while ensure_approved_community_member and ensure_admin return 403. The crowdin_username check was the only place that crossed it.
  • On the security question raised in the issue: neither status discloses more than the other. Both sit behind authentication, and ensure_user_exists already returns 404 for an unknown user before this check is reached, so the change reveals nothing new about which accounts exist.
  • I audited every 401 and 403 in contributors/app/ to confirm this does not simply relocate the inconsistency. After this change the rule holds without exception. Every 401 is an authentication failure: the two logged_in? guards in ApiApplicationController, and the onboarding trigger token comparison. Every 403 is an authorization failure on an authenticated caller: approved_community_member?, admin?, owner?, admin-or-owner, and now admin-or-self.

The 'an endpoint requiring login' shared example is deliberately untouched and still asserts 401. That covers the genuinely unauthenticated request, which is a different case and keeps the correct status.

Differences :

This changes an API response status and has no UI surface, so there is nothing to screenshot.

  • Before: PATCH /api/v1/users/:id/crowdin_username returned 401 Unauthorized when an authenticated user who was neither the owner nor an admin made the request.
  • After: the same request returns 403 Forbidden. The unauthenticated case is unaffected and still returns 401 Unauthorized.

Validation steps

Automated:

bundle exec rspec spec/requests/api/v1/users_controller_spec.rb

Manual, against a local environment:

  1. Sign in as a non-admin user.
  2. Send PATCH /api/v1/users/:id/crowdin_username using a different user's ID.
  3. The response is now 403 Forbidden; it was previously 401 Unauthorized.
  4. Sign out and repeat the same request. It still returns 401 Unauthorized, confirming the authentication and authorization cases stay distinct.
  5. Repeat step 2 as an admin, and as the owning user. Both still return 200 OK.
Edited by Jeston Singh

Merge request reports

Loading
Loading