Skip to content

Make dashboards, leaderboards, and profiles public#831

Merged
JoaquinBN merged 3 commits into
devfrom
JoaquinBN/deleted-validator
Jun 23, 2026
Merged

Make dashboards, leaderboards, and profiles public#831
JoaquinBN merged 3 commits into
devfrom
JoaquinBN/deleted-validator

Conversation

@JoaquinBN

@JoaquinBN JoaquinBN commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Updates the unauthenticated experience so Overview, role dashboards, leaderboards, public profiles, and search can be browsed without connecting a wallet.

Keeps contribution submission, protected subsections, steward workflows, referrals, and contribution detail flows behind authentication.

Moves builder, validator, and community leaderboards into the public leaderboard view and removes validator waitlist dashboard surfaces.

Adds public-safe API responses for metrics, steward aggregates, user profiles/search, and leaderboard data with visibility filtering and throttling.

Summary by CodeRabbit

  • New Features
    • Expanded public access across leaderboard, metrics, steward submissions stats/daily metrics, user profiles/search, and stewards/contribution type listings.
    • Portal contributions analytics now loads for everyone.
  • Bug Fixes
    • Improved reliability of the public metrics overview payload with safer fallback behavior.
  • Style
    • Updated navigation visibility and routing for global vs steward areas; refined auth-aware sidebar/header UI.
  • Tests
    • Extended coverage for anonymous access rules, date-range validation, privacy/visibility behavior, and rate-limited public profile/search.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JoaquinBN, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 20 minutes and 32 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a89f9127-a351-4b77-ac72-b45eafb5fbd5

📥 Commits

Reviewing files that changed from the base of the PR and between c293fd1 and 335a5ed.

📒 Files selected for processing (2)
  • backend/users/tests/test_email_security.py
  • backend/users/views.py
📝 Walkthrough

Walkthrough

This PR makes multiple backend API endpoints and frontend routes publicly accessible without authentication. Backend changes relax permission classes on leaderboard, contribution types, steward submissions, and user profile endpoints, while adding visibility filters and email-field protection for unauthenticated callers. Frontend route guards are removed for listing and leaderboard pages, and navigation components are updated to show auth-gated write actions only to authenticated users.

Changes

Public Access Rollout

Layer / File(s) Summary
Backend API permission relaxation and public aggregate logic
backend/leaderboard/views.py, backend/contributions/views.py, backend/stewards/views.py, backend/leaderboard/tests/test_stats.py, backend/contributions/tests/test_canceled_submissions.py, backend/contributions/tests/test_is_submittable.py, backend/contributions/tests/test_steward_permissions.py, backend/stewards/tests.py
LeaderboardViewSet, ContributionTypeViewSet, and StewardSubmissionViewSet stats/daily_metrics are switched from IsAuthenticated to AllowAny; daily_metrics also drops authentication_classes; StewardViewSet.list filters by user__visible=True; user_stats and user_stats_by_address enforce visibility via _can_view_user_stats helper; stats endpoint branches between steward-scoped and public aggregate logic; tests updated to assert 200 for anonymous callers.
Overview metrics endpoint resilience
backend/api/metrics_views.py, backend/api/tests.py
OverviewMetricsView.get wraps payload construction in try/except, falling back to build_overview_payload() if no latest snapshot exists and returning empty_overview_payload() on any exception; test updated to assert live counts are returned without a pre-built aggregate.
UserViewSet public actions, throttling, and email security
backend/users/views.py, backend/tally/settings.py, backend/users/tests/test_email_security.py, backend/users/serializers.py
get_permissions() exposes retrieve/by_address/user_highlights/search as AllowAny; get_throttles() assigns scope per action; public_user_search and public_user_profile throttle rates added; user_highlights raises Http404 for invisible users; referrals changed to AllowAny; to_representation() removes referral fields for public profiles; email security tests verify sensitive fields absent for anonymous callers and hidden users return 404.
Frontend route guard changes
frontend/src/App.svelte
protectedRoute removed for /testnets, /leaderboard, /community, /community/leaderboard, /builders, /builders/leaderboard, /validators, /validators/leaderboard, and /participant/:address; protectedRoute added to /stewards/submissions, /stewards/feature-reviews, and /stewards/discord-xp.
Navbar, Sidebar, AuthButton auth-aware UI
frontend/src/components/Navbar.svelte, frontend/src/components/Sidebar.svelte, frontend/src/components/AuthButton.svelte
SearchBar rendered unconditionally; "Submit contribution" button gated to authenticated users in both Navbar and Sidebar; Sidebar sub-items for Builders/Validators/Community shown only when authenticated; per-category leaderboard links removed from sub-menus; unified /leaderboard link added to Overview section; AuthButton unauthenticated state receives new sizing styles.
SectionHeader auth-aware navigation and Dashboard refactor
frontend/src/components/ui/SectionHeader.svelte, frontend/src/routes/Dashboard.svelte
SectionHeader gains requiresAuth prop and handleLinkClick() that stores redirectAfterLogin and triggers the auth UI for unauthenticated clicks. Dashboard removes validator waitlist fetch, adds community-only contribution slider using PortalContributionCard, and switches leaderboard path references to ?type= query-string format.
Leaderboard URL-driven category and Metrics public analytics
frontend/src/routes/Leaderboard.svelte, frontend/src/routes/Metrics.svelte
Leaderboard derives selectedCategory from URL ?type= param, removes global/steward categories, and adds a category button bar calling selectCategory which pushes new routes. Metrics removes canViewSubmissionAnalytics gating so Portal contributions section and submission analytics fetch always execute.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Unauthenticated Client
    participant App as App.svelte
    participant SectionHeader as SectionHeader
    participant UserAPI as UserViewSet
    participant StewardAPI as StewardSubmissionViewSet

    Client->>App: Navigate to /leaderboard
    App->>Client: Render public Leaderboard (no protectedRoute)

    Client->>SectionHeader: Click "View all" with requiresAuth=true
    SectionHeader->>SectionHeader: handleLinkClick() — not authenticated
    SectionHeader->>Client: Set sessionStorage.redirectAfterLogin
    SectionHeader->>Client: Trigger [data-auth-button] click

    Client->>UserAPI: GET /api/v1/users/{address}/
    UserAPI->>UserAPI: get_permissions() → AllowAny for retrieve
    UserAPI->>UserAPI: _can_view_user() check visibility
    UserAPI->>Client: 200 public profile (email fields absent)

    Client->>StewardAPI: GET /api/v1/steward-submissions/stats/
    StewardAPI->>StewardAPI: permission_classes=[AllowAny]
    StewardAPI->>StewardAPI: User not steward → public aggregates
    StewardAPI->>StewardAPI: Query SubmittedContribution(user__visible=True)
    StewardAPI->>Client: 200 public stats with acceptance_rate
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • genlayer-foundation/points#793: Directly modifies StewardSubmissionViewSet.daily_metrics in backend/contributions/views.py, the same endpoint whose public access and query logic are changed in this PR.
  • genlayer-foundation/points#762: Updates StewardSubmissionViewSet access control and visibility boundaries in backend/contributions/views.py, related to the steward query-scoping logic introduced here.
  • genlayer-foundation/points#733: Modifies tests for /api/v1/steward-submissions/stats/ endpoint permissions and behavior, directly affected by this PR's permission class and logic changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: making dashboards, leaderboards, and profiles accessible without authentication.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch JoaquinBN/deleted-validator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
backend/contributions/views.py (1)

71-84: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Filter submission_count to public-visible users before exposing this view.

With Line 71 making the endpoint anonymous, active_submissions still counts submissions from hidden users. Add user__visible=True before the aggregate so public contribution-type counts do not reveal hidden profile activity.

Proposed fix
-        active_submissions = SubmittedContribution.objects.exclude(
+        active_submissions = SubmittedContribution.objects.filter(
+            user__visible=True,
+        ).exclude(
             state__in=['rejected', 'canceled']
         )

As per path instructions, “Flag unauthenticated routes exposing user data.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/contributions/views.py` around lines 71 - 84, The get_queryset method
in this view is accessible to unauthenticated users due to the AllowAny
permission class, but the active_submissions queryset counts submissions from
all users including those with hidden profiles. This leaks information about
hidden user activity. Filter the active_submissions queryset by adding
user__visible=True before the aggregate annotation to ensure the
submission_count only reflects contributions from publicly visible users.

Source: Path instructions

backend/leaderboard/views.py (1)

63-68: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the anonymous leaderboard list response.

The viewset is now public, but limit is optional and unbounded, so /leaderboard/ can still return every visible entry. Clamp offset/limit and apply a safe default page size for anonymous access.

Proposed bounds
-        offset = 0
-        limit = None
+        offset = 0
+        limit = 100
         try:
-            offset = int(self.request.query_params.get('offset', 0))
+            offset = max(int(self.request.query_params.get('offset', 0)), 0)
         except (ValueError, TypeError):
             offset = 0
 
         limit_param = self.request.query_params.get('limit')
         if limit_param:
             try:
-                limit = int(limit_param)
+                limit = min(max(int(limit_param), 1), 100)
             except (ValueError, TypeError):
-                limit = None
+                limit = 100

As per path instructions, “Flag missing input validation, unsafe filtering, dynamic ordering/search abuse.”

Also applies to: 151-174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/leaderboard/views.py` around lines 63 - 68, The leaderboard viewset
permits anonymous access with pagination disabled, allowing unrestricted
retrieval of all entries. Replace the `pagination_class = None` setting with a
proper pagination class that enforces a maximum page size for anonymous users.
Implement explicit bounds on the offset and limit parameters to prevent abuse,
and apply a safe default page size (e.g., 100 entries) for anonymous requests.
Ensure the same pagination bounds are applied to the related viewset code
mentioned at lines 151-174 to maintain consistent access controls across all
public leaderboard endpoints.

Source: Path instructions

backend/users/views.py (2)

136-159: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Finish public hardening for user_highlights.

The new _can_view_user check only authorizes the profile. The response still serializes every ContributionHighlight for that user, and limit can be non-numeric, negative, or huge. Before exposing this action publicly, apply the same public contribution/highlight visibility predicate used elsewhere and clamp or reject invalid limits.

Proposed `limit` guard
-        limit = int(request.query_params.get('limit', 5))
+        try:
+            limit = int(request.query_params.get('limit', 5))
+        except (TypeError, ValueError):
+            return Response(
+                {'error': 'Invalid limit'},
+                status=status.HTTP_400_BAD_REQUEST
+            )
+        limit = min(max(limit, 1), 20)

As per path instructions, "Flag any handler/viewset/action that fetches a resource by id, slug, address, wallet, username, path param, or query param without proving object ownership, steward/admin role, or an explicit permission check on that exact resource" and "Flag missing input validation, unsafe filtering, dynamic ordering/search abuse."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/users/views.py` around lines 136 - 159, The user_highlights action
has two security issues that need to be addressed before public exposure. First,
the queryset filters by user but does not apply the public
contribution/highlight visibility predicate that is used elsewhere in the
codebase to control which highlights are visible based on their public/private
status. Second, the limit parameter extracted from request.query_params has no
input validation and can accept non-numeric, negative, or excessively large
values. Add the appropriate visibility filter to the queryset based on the same
predicate used elsewhere, and add validation to clamp the limit parameter to a
reasonable range or reject invalid input before using it to slice the queryset.

Source: Path instructions


892-909: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Cap and stabilize anonymous search queries.

Making search public exposes an unbounded multi-field icontains query plus a related-table lookup. Add a max query length and return stable distinct rows to reduce scraping/load and duplicate users from the join.

Proposed fix
         query = request.query_params.get('q', '').strip()
 
         if len(query) < 2:
             return Response([])
+
+        max_query_length = 64
+        if len(query) > max_query_length:
+            return Response(
+                {'error': 'Search query is too long'},
+                status=status.HTTP_400_BAD_REQUEST
+            )
 
         search_query = (
             Q(name__icontains=query) |
             Q(address__icontains=query) |
             Q(twitter_handle__icontains=query) |
@@
-        users = User.objects.filter(search_query).filter(visible=True)[:10]
+        users = (
+            User.objects
+            .filter(search_query, visible=True)
+            .distinct()
+            .order_by('name', 'id')
+            .only('id', 'name', 'address', 'profile_image_url')[:10]
+        )

As per path instructions, "Flag missing input validation, unsafe filtering, dynamic ordering/search abuse."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/users/views.py` around lines 892 - 909, The search method lacks both
input validation for maximum query length and deduplication of results. Add a
maximum length check for the query parameter after the existing minimum length
check (similar to how len(query) < 2 is validated), and add .distinct() to the
User.objects.filter() queryset chain before the slice operation to eliminate
duplicate rows that can result from the join with
githubconnection__platform_username. This will cap unbounded search queries and
ensure stable, non-duplicated results.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/api/metrics_views.py`:
- Around line 43-48: Update the docstring of the class containing the metrics
view to accurately reflect the new fallback behavior. The docstring currently
states that public reads never aggregate source data, but the code now calls
build_overview_payload() when latest_overview_payload() returns None, which
performs live aggregation. Modify the class docstring to clarify that when no
snapshot exists, the view performs a live rebuild of the overview payload via
build_overview_payload(), which does aggregate source data in real-time.

In `@backend/contributions/tests/test_steward_permissions.py`:
- Around line 121-125: The test method names are misleading because they suggest
"cannot access steward endpoints" but the actual test assertions validate
successful public access to the /stats/ endpoint with HTTP 200 OK. Rename the
test methods to accurately reflect the intent of testing mixed access behavior,
specifically that the steward statistics endpoint is publicly accessible. Apply
this rename to all affected test methods as indicated by the comment, updating
the names to clearly convey that they verify public access to certain endpoints
rather than blocking access.

In `@backend/contributions/views.py`:
- Around line 2407-2413: The daily_metrics action lacks input validation on its
date range parameters, allowing callers to request extremely large spans
(especially with group_by=day) that could cause memory exhaustion. Add
validation in the daily_metrics method to reject inverted date ranges (where
start_date is after end_date) and implement maximum allowed span limits based on
the group_by parameter value (such as stricter limits for day grouping, looser
limits for month grouping). This validation should occur before processing the
date range logic to prevent the allocation of large responses.

In `@backend/leaderboard/views.py`:
- Line 63: The permission_classes setting of AllowAny on line 63 exposes the
user_stats and user_stats_by_address actions publicly, but these actions do not
validate user visibility before returning stats data. In the user_stats and
user_stats_by_address action methods (around lines 618-650), add a visibility
check before returning the stats using the same visibility validation logic
applied to public profiles. This ensures that hidden users' statistics are not
exposed through the anonymous API endpoints.

In `@backend/users/views.py`:
- Around line 43-46: The get_permissions method allows public access to retrieve
and by_address actions, but the serializers being used for these actions still
include referral details which should only be exposed to authenticated users. To
fix this, modify the get_serializer_class method to return a different
serializer for public actions (retrieve, by_address) that excludes
referral-related fields, while keeping the full serializer with referral details
for authenticated-only endpoints like the owner/staff-only path. This ensures
referral information is only accessible through restricted endpoints and not
through the public profile endpoints.

In `@frontend/src/components/AuthButton.svelte`:
- Around line 197-202: The `.auth-button:not(.connected)` CSS rule for
unauthenticated button sizing violates the Tailwind-only styling guideline for
Svelte components. Remove this custom CSS rule from the style block and instead
apply the equivalent Tailwind utility classes directly to the button element's
class attribute when in the unauthenticated state. Replace the custom min-width,
height, padding, and font-size properties with their Tailwind equivalents such
as min-w-[168px], h-11, px-5, py-2.5, and text-[15px], or adjust to the nearest
Tailwind scale values as preferred.

In `@frontend/src/components/Sidebar.svelte`:
- Around line 220-222: The anchor element for the leaderboard link is
unnecessarily preventing default behavior and manually calling navigate.
Simplify this by removing the onclick handler that contains e.preventDefault()
and navigate('/leaderboard'), and keep the anchor as a plain in-app link with
just href="/leaderboard". This allows the global link interceptor to handle the
routing centrally. Apply the same fix to the other similar anchor instances
mentioned at lines 677-679.

In `@frontend/src/components/ui/SectionHeader.svelte`:
- Around line 15-23: The handleLinkClick() function is calling push(linkPath)
for all clicks, which violates the coding guidelines requiring plain anchor tags
for navigation. Refactor the rendered element to use a standard <a
href={linkPath}> anchor tag instead of calling push() in the handler. Modify the
handleLinkClick() function to only preventDefault() and trigger login when
requiresAuth is true and the user is unauthenticated; otherwise, allow the
anchor tag's default navigation behavior to handle the routing through the
global SPA interceptor.

In `@frontend/src/routes/Metrics.svelte`:
- Around line 301-305: The fetchSubmissionsData() function call at line 305
needs to be updated to align with the backend contract. For anonymous users, the
backend returns 403 when requesting full metrics without the summary_only
parameter. Check if the user is authenticated before calling
fetchSubmissionsData, and if unauthenticated, either pass summary_only: true as
a parameter to the function call or conditionally request only summary metrics
for anonymous users. This ensures the frontend respects the backend's permission
model and prevents page-level errors for unauthenticated users viewing the
metrics page. Apply the same fix to the other fetchSubmissionsData calls
mentioned in the comment (around lines 1768 and 1924).

---

Outside diff comments:
In `@backend/contributions/views.py`:
- Around line 71-84: The get_queryset method in this view is accessible to
unauthenticated users due to the AllowAny permission class, but the
active_submissions queryset counts submissions from all users including those
with hidden profiles. This leaks information about hidden user activity. Filter
the active_submissions queryset by adding user__visible=True before the
aggregate annotation to ensure the submission_count only reflects contributions
from publicly visible users.

In `@backend/leaderboard/views.py`:
- Around line 63-68: The leaderboard viewset permits anonymous access with
pagination disabled, allowing unrestricted retrieval of all entries. Replace the
`pagination_class = None` setting with a proper pagination class that enforces a
maximum page size for anonymous users. Implement explicit bounds on the offset
and limit parameters to prevent abuse, and apply a safe default page size (e.g.,
100 entries) for anonymous requests. Ensure the same pagination bounds are
applied to the related viewset code mentioned at lines 151-174 to maintain
consistent access controls across all public leaderboard endpoints.

In `@backend/users/views.py`:
- Around line 136-159: The user_highlights action has two security issues that
need to be addressed before public exposure. First, the queryset filters by user
but does not apply the public contribution/highlight visibility predicate that
is used elsewhere in the codebase to control which highlights are visible based
on their public/private status. Second, the limit parameter extracted from
request.query_params has no input validation and can accept non-numeric,
negative, or excessively large values. Add the appropriate visibility filter to
the queryset based on the same predicate used elsewhere, and add validation to
clamp the limit parameter to a reasonable range or reject invalid input before
using it to slice the queryset.
- Around line 892-909: The search method lacks both input validation for maximum
query length and deduplication of results. Add a maximum length check for the
query parameter after the existing minimum length check (similar to how
len(query) < 2 is validated), and add .distinct() to the User.objects.filter()
queryset chain before the slice operation to eliminate duplicate rows that can
result from the join with githubconnection__platform_username. This will cap
unbounded search queries and ensure stable, non-duplicated results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 94bef21f-2651-4f56-954f-819cbfae0f51

📥 Commits

Reviewing files that changed from the base of the PR and between fb89eaf and ec40c3a.

📒 Files selected for processing (21)
  • backend/api/metrics_views.py
  • backend/api/tests.py
  • backend/contributions/tests/test_canceled_submissions.py
  • backend/contributions/tests/test_is_submittable.py
  • backend/contributions/tests/test_steward_permissions.py
  • backend/contributions/views.py
  • backend/leaderboard/tests/test_stats.py
  • backend/leaderboard/views.py
  • backend/stewards/tests.py
  • backend/stewards/views.py
  • backend/tally/settings.py
  • backend/users/tests/test_email_security.py
  • backend/users/views.py
  • frontend/src/App.svelte
  • frontend/src/components/AuthButton.svelte
  • frontend/src/components/Navbar.svelte
  • frontend/src/components/Sidebar.svelte
  • frontend/src/components/ui/SectionHeader.svelte
  • frontend/src/routes/Dashboard.svelte
  • frontend/src/routes/Leaderboard.svelte
  • frontend/src/routes/Metrics.svelte

Comment thread backend/api/metrics_views.py
Comment thread backend/contributions/tests/test_steward_permissions.py
Comment thread backend/contributions/views.py
Comment thread backend/leaderboard/views.py
Comment thread backend/users/views.py
Comment thread frontend/src/components/AuthButton.svelte Outdated
Comment thread frontend/src/components/Sidebar.svelte Outdated
Comment thread frontend/src/components/ui/SectionHeader.svelte Outdated
Comment on lines +301 to +305
const typesResponse = await api.get('/contribution-types/', { params: { page_size: 1000 } });
contributionTypes = normalizeContributionTypes(
typesResponse.data.results || typesResponse.data || []
);
await fetchSubmissionsData({ syncDates: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Align the public metrics request with the backend contract.

fetchSubmissionsData() still calls /steward-submissions/daily-metrics/ without summary_only, while the supplied backend view returns 403 for unauthenticated non-summary requests. With the section now rendered unconditionally, anonymous users can hit the page-level error path instead of seeing the public metrics page. Either make the backend allow this exact public full-metrics request, or have the frontend request/render only the public summary_only contract for anonymous users.

Also applies to: 1768-1768, 1924-1924

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/routes/Metrics.svelte` around lines 301 - 305, The
fetchSubmissionsData() function call at line 305 needs to be updated to align
with the backend contract. For anonymous users, the backend returns 403 when
requesting full metrics without the summary_only parameter. Check if the user is
authenticated before calling fetchSubmissionsData, and if unauthenticated,
either pass summary_only: true as a parameter to the function call or
conditionally request only summary metrics for anonymous users. This ensures the
frontend respects the backend's permission model and prevents page-level errors
for unauthenticated users viewing the metrics page. Apply the same fix to the
other fetchSubmissionsData calls mentioned in the comment (around lines 1768 and
1924).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/users/views.py (1)

43-54: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate and cap limit for the now-public highlights endpoint.

Line 140 parses untrusted input with int(...) and no guard; malformed values can raise a server error, and unbounded values can amplify query/load on an unauthenticated route.

Suggested fix
@@
-        limit = int(request.query_params.get('limit', 5))
+        try:
+            limit = int(request.query_params.get('limit', 5))
+        except (TypeError, ValueError):
+            return Response(
+                {'error': 'Invalid limit value'},
+                status=status.HTTP_400_BAD_REQUEST,
+            )
+        limit = max(1, min(limit, 50))
@@
-        highlights = queryset[:limit]
+        highlights = queryset[:limit]

As per path instructions, "Flag missing input validation, unsafe filtering, dynamic ordering/search abuse, and SQL/ORM/raw-query injection risks from string-built queries."

Also applies to: 140-141, 159-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/users/views.py` around lines 43 - 54, Add input validation and
enforce a maximum limit cap for the public highlights endpoint and search
endpoint. Locate where the limit parameter is parsed using int() around line 140
(in the user_highlights action handler) and lines 159-160 (in the search action
handler). For both locations, wrap the int() conversion in a try-except block to
handle malformed input gracefully, and enforce a maximum cap on the limit value
to prevent unbounded query amplification on unauthenticated routes. Return an
appropriate error response for invalid limit values instead of allowing the
server to raise an exception.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/users/views.py`:
- Around line 43-54: Add input validation and enforce a maximum limit cap for
the public highlights endpoint and search endpoint. Locate where the limit
parameter is parsed using int() around line 140 (in the user_highlights action
handler) and lines 159-160 (in the search action handler). For both locations,
wrap the int() conversion in a try-except block to handle malformed input
gracefully, and enforce a maximum cap on the limit value to prevent unbounded
query amplification on unauthenticated routes. Return an appropriate error
response for invalid limit values instead of allowing the server to raise an
exception.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 138d062e-8eb4-442f-9b0a-874dc44bf401

📥 Commits

Reviewing files that changed from the base of the PR and between ec40c3a and c293fd1.

📒 Files selected for processing (12)
  • backend/api/metrics_views.py
  • backend/contributions/tests/test_canceled_submissions.py
  • backend/contributions/tests/test_steward_permissions.py
  • backend/contributions/views.py
  • backend/leaderboard/tests/test_stats.py
  • backend/leaderboard/views.py
  • backend/users/serializers.py
  • backend/users/tests/test_email_security.py
  • backend/users/views.py
  • frontend/src/components/AuthButton.svelte
  • frontend/src/components/Sidebar.svelte
  • frontend/src/components/ui/SectionHeader.svelte
💤 Files with no reviewable changes (1)
  • frontend/src/components/Sidebar.svelte

@JoaquinBN JoaquinBN merged commit e4cdadb into dev Jun 23, 2026
2 of 3 checks passed
@JoaquinBN JoaquinBN deleted the JoaquinBN/deleted-validator branch June 23, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant