Skip to content

Conversation

@visz11
Copy link
Contributor

@visz11 visz11 commented Dec 24, 2025

CodeAnt-AI Description

Add user profile endpoints and a bulk admin action endpoint

What Changed

  • New GET /users/profile returns user id, email, first and last name, active status, and account creation date
  • New PUT /users/profile lets the authenticated user update their email, first name, and last name; returns a success message or a 400 error on failure
  • New POST /users/bulk-action allows admins to disable or delete multiple users in one request and returns per-user results; non-admins receive a 403 error

Impact

✅ Users can update their email and name without support
✅ Admins can disable or delete multiple accounts in one request
✅ Requests surface account active status and creation time for clearer profiles

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features
    • Users can retrieve and view their complete profile information including personal details and account settings
    • Users can update their profile with new email addresses and name information
    • Administrators can now perform bulk user management operations, enabling them to disable or delete multiple user accounts in a single batch operation

✏️ Tip: You can customize this high-level summary in your review settings.

@codeant-ai
Copy link

codeant-ai bot commented Dec 24, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

A new FastAPI router for user management is introduced with three endpoints: GET /profile retrieves current user details from Firebase Auth, PUT /profile updates user information and custom claims, and POST /bulk-action performs batch operations (disable/delete) on multiple users with admin-only access.

Changes

Cohort / File(s) Summary
User Management Router
app/auth/user_management.py
Introduces FastAPI router with three endpoints: GET /profile (fetch user details from Firebase), PUT /profile (update email and custom claims), and POST /bulk-action (admin-only batch operations). Includes UserUpdateRequest and BulkUserRequest data models, dependency-based auth checks, and Firebase Admin SDK error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A router hops in, three endpoints in tow,
Profiles to fetch, updates to flow,
Admins may bulge the user data with care,
Firebase claims scattered everywhere!
Security checks keep the wrong ones at bay,
Fresh user management—hip-hip-hooray! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementation of user management endpoints in FastAPI, which aligns with the core functionality added (three endpoints for user profile and bulk actions).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test-branch-cc8

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 and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes a foundational user management system for the application by introducing a new module with several key API endpoints. It allows authenticated users to access and modify their personal profiles, while also providing administrators with powerful tools to manage user accounts in bulk. The implementation relies on Firebase Authentication to securely handle user data and ensures proper access control for sensitive operations.

Highlights

  • New User Management Module: Introduced a new Python file, 'app/auth/user_management.py', dedicated to handling user-related operations within the application.
  • User Profile Endpoints: Implemented two new endpoints: 'GET /users/profile' to retrieve detailed information about the current user, and 'PUT /users/profile' to allow users to update their email, first name, and last name.
  • Admin Bulk Actions: Added a 'POST /users/bulk-action' endpoint, restricted to admin users, enabling them to perform bulk operations such as disabling or deleting multiple user accounts.
  • Firebase Integration: All user management functionalities, including profile retrieval, updates, and bulk actions, are integrated with Firebase Authentication for backend user data handling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@refacto-visz
Copy link

refacto-visz bot commented Dec 24, 2025

Refacto PR Summary

Implemented comprehensive user management endpoints in FastAPI with Firebase Authentication integration to enable user profile management and administrative bulk operations. The implementation provides secure profile CRUD operations for authenticated users and admin-only bulk user management capabilities.

Key Changes:

  • Added /users/profile GET/PUT endpoints for user profile management with Firebase custom claims integration
  • Implemented /users/bulk-action endpoint for admin-only bulk user operations (disable/delete)
  • Created UserUpdateRequest and BulkUserRequest Pydantic models for request validation
  • Integrated Firebase Admin SDK for user record management and custom claims handling
  • Added role-based access control with admin privilege verification for bulk operations

Change Highlights

Click to expand
  • app/auth/user_management.py: Complete user management API with profile and bulk operations
  • UserUpdateRequest model: Email and name validation for profile updates
  • BulkUserRequest model: Multi-user action request structure
  • get_user_profile(): Firebase user record retrieval with custom claims
  • update_user_profile(): Profile modification with email and custom claims updates
  • bulk_user_action(): Admin-only bulk disable/delete operations with error handling

Sequence Diagram

sequenceDiagram
    participant U as User
    participant API as FastAPI
    participant Auth as Firebase Auth
    participant FB as Firebase Admin
    
    U->>API: GET /users/profile
    API->>Auth: Verify JWT token
    Auth-->>API: User data
    API->>FB: get_user(uid)
    FB-->>API: User record + custom claims
    API-->>U: Profile data
    
    U->>API: PUT /users/profile
    API->>Auth: Verify JWT token
    API->>FB: update_user() + set_custom_user_claims()
    FB-->>API: Success
    API-->>U: Profile updated
    
    Note over U,FB: Admin bulk operations
    U->>API: POST /users/bulk-action
    API->>Auth: Verify admin role
    API->>FB: update_user(disabled=True) / delete_user()
    FB-->>API: Operation results
    API-->>U: Bulk operation results
Loading

Testing Guide

Click to expand
  1. Profile retrieval: Authenticate as user, GET /users/profile, verify Firebase user data and custom claims returned
  2. Profile update: PUT /users/profile with new email/name, confirm Firebase user record and custom claims updated
  3. Admin bulk disable: Authenticate as admin, POST /users/bulk-action with action="disable", verify users disabled in Firebase
  4. Non-admin bulk action: Authenticate as regular user, attempt bulk action, confirm 403 Forbidden response
  5. Bulk operation error handling: Include invalid user IDs in bulk request, verify partial success with error details

@refacto-visz
Copy link

refacto-visz bot commented Dec 24, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@codeant-ai codeant-ai bot added the size:M This PR changes 30-99 lines, ignoring generated files label Dec 24, 2025
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces new user management endpoints. While the functionality is a good addition, there are several critical issues that need to be addressed. The most significant problems are KeyError bugs that will cause endpoints to fail due to incorrect dictionary key access ('id' instead of 'uid'). Additionally, exception handling is consistently too broad, which can mask bugs and leak sensitive information. I've also suggested improvements for authorization logic, data validation using Literal types, and safer access to user claims. Addressing these points will significantly improve the robustness and security of the new endpoints.

async def get_user_profile(current_user: Dict[str, Any] = Depends(get_current_user)):
"""Get detailed user profile information"""
try:
user_record = auth.get_user(current_user["id"])

Choose a reason for hiding this comment

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

critical

The current_user dictionary returned by the get_current_user dependency uses 'uid' as the key for the user ID, not 'id'. Accessing current_user["id"] will result in a KeyError and cause the endpoint to fail.

Suggested change
user_record = auth.get_user(current_user["id"])
user_record = auth.get_user(current_user["uid"])

):
"""Update current user's profile information"""
try:
user_record = auth.get_user(current_user["id"])

Choose a reason for hiding this comment

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

critical

Similar to the profile retrieval endpoint, the current_user dictionary uses 'uid' as the key for the user ID, not 'id'. This will cause a KeyError.

Suggested change
user_record = auth.get_user(current_user["id"])
user_record = auth.get_user(current_user["uid"])

Comment on lines +31 to +32
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],

Choose a reason for hiding this comment

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

high

Accessing custom claims directly with [] can cause a KeyError if a claim is not set for a user. It's safer to use the .get() method to avoid this and provide a graceful fallback (it will return null in the JSON if the key is missing).

Suggested change
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
"first_name": custom_claims.get("first_name"),
"last_name": custom_claims.get("last_name"),

Comment on lines +36 to +37
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))

Choose a reason for hiding this comment

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

high

Catching a generic Exception is too broad and can hide underlying issues. It's better to catch specific exceptions, like auth.UserNotFoundError. Additionally, exposing raw exception messages via str(e) can be a security risk. Provide a generic error message for unexpected server errors.

Suggested change
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
except auth.UserNotFoundError:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
except Exception:
# It's good practice to log the actual error here for debugging purposes.
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal error occurred.")

Comment on lines +56 to +57
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))

Choose a reason for hiding this comment

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

high

The exception handling is too broad. Catching specific exceptions like auth.UserNotFoundError would provide more accurate error responses. Also, returning HTTP_400_BAD_REQUEST for any server-side failure might be misleading; a 5xx error would be more appropriate for internal errors. Avoid exposing raw exception details.

Suggested change
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except auth.UserNotFoundError:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
except Exception:
# It's good practice to log the actual error here for debugging purposes.
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Failed to update profile.")

Comment on lines +77 to +78
except Exception as e:
results.append({"user_id": user_id, "status": "error", "message": str(e)})

Choose a reason for hiding this comment

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

high

The exception handling here is too broad. Catching specific exceptions like auth.UserNotFoundError will provide more meaningful error messages in the bulk operation results. Also, avoid exposing raw exception details in the response.

Suggested change
except Exception as e:
results.append({"user_id": user_id, "status": "error", "message": str(e)})
except auth.UserNotFoundError:
results.append({"user_id": user_id, "status": "error", "message": "User not found"})
except Exception:
results.append({"user_id": user_id, "status": "error", "message": "An unexpected error occurred."})

@@ -0,0 +1,79 @@
from fastapi import APIRouter, Depends, HTTPException, status
from .firebase_auth import firebase_auth

Choose a reason for hiding this comment

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

medium

The firebase_auth import is unused in this file and can be removed to improve code cleanliness.

from fastapi import APIRouter, Depends, HTTPException, status
from .firebase_auth import firebase_auth
from .dependencies import get_current_user, require_admin
from typing import Dict, Any, List

Choose a reason for hiding this comment

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

medium

To enable strong typing for the action field in BulkUserRequest, Literal should be imported from the typing module.

Suggested change
from typing import Dict, Any, List
from typing import Dict, Any, List, Literal


class BulkUserRequest(BaseModel):
user_ids: List[str]
action: str

Choose a reason for hiding this comment

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

medium

Using a plain string for the action field is prone to errors. By using Literal['disable', 'delete'], you make the API contract explicit and leverage FastAPI's built-in validation to ensure only valid actions are submitted.

Suggested change
action: str
action: Literal["disable", "delete"]

Comment on lines +61 to +67
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")

Choose a reason for hiding this comment

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

medium

This endpoint performs a manual authorization check for the admin role. It's better to use the require_admin dependency, which is already imported. This approach centralizes authorization logic, makes the endpoint's intent clearer, and is more idiomatic for FastAPI.

Suggested change
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
async def bulk_user_action(
request: BulkUserRequest,
_: Dict[str, Any] = Depends(require_admin)
):
"""Perform bulk actions on multiple users (admin only)"""

@codeant-ai
Copy link

codeant-ai bot commented Dec 24, 2025

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Possible Bug
    The code calls a non-existent/incorrect Firebase Admin API to read custom claims (auth.get_custom_user_claims(...)). In the Python Firebase Admin SDK, custom claims are returned on the UserRecord object (e.g., user_record.custom_claims). This will raise an error or always return None and lead to KeyError when accessing custom_claims["first_name"]/["last_name"].

  • Overwriting Claims
    When updating profile the code calls auth.set_custom_user_claims(...) with only first_name, last_name and role, which overwrites any other existing custom claims (e.g., permissions, flags). This can unintentionally remove previously-set claims.

  • Authorization & Validation
    Bulk actions rely on a manual current_user.get("role") check (instead of using the imported require_admin dependency) and the action field is not validated for unsupported values (invalid actions are silently ignored). This can lead to inconsistent behavior or insufficient enforcement of admin-only operations.

@refacto-visz
Copy link

refacto-visz bot commented Dec 24, 2025

Code Review: Authentication Service - Critical Reliability & Security Issues

PR Confidence Score: 🟥 1 / 5

👍 Well Done
Structured Error Handling

Comprehensive try-catch blocks prevent unhandled exceptions effectively

Async Endpoint Design

FastAPI async endpoints enable concurrent request handling for better throughput

Clear API Structure

Well-organized router with logical endpoint grouping and consistent naming conventions

📁 Selected files for review (1)
  • app/auth/user_management.py
🎯 Custom Instructions
❌ Unapplied Instructions
Organization Guidelines

Reason: Your set path patterns [src/, config/] don't match any selected files for review; Your set extension patterns [.java] don't match any selected files for review

📝 Additional Comments
app/auth/user_management.py (4)
Generic Exception Handling

Generic exception handling masks specific Firebase errors and exposes internal error details to clients. Authentication failures become indistinguishable from system errors reducing debugging capability. Sensitive error information potentially leaked to unauthorized users.

Standards:

  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling
Context References
  • get_user_profile function - catches all exceptions without differentiation in app/auth/user_management.py
Redundant Firebase API Calls

Two separate Firebase API calls to retrieve user data and custom claims for same user. Firebase get_user() response includes custom claims data making second call redundant. This doubles network latency and API quota consumption for profile retrieval.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-API-Consolidation
Unnecessary User Lookup

Firebase user lookup performed when current_user data already available from authentication dependency. The get_current_user dependency likely already retrieved user information making this additional API call wasteful. Increases response time and Firebase API usage unnecessarily.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-Data-Reuse
Context References
  • get_current_user - returns user data from token verification in app/auth/dependencies.py
Email Uniqueness Validation

Email update logic lacks validation for email uniqueness constraints. Firebase may reject duplicate emails, but business logic should validate uniqueness before attempting update to provide clearer error messaging.

Standards:

  • Business-Rule-Constraint-Validation
  • Data-Validation-Uniqueness-Checks
🧰 Additional context used
app/auth/user_management.py (6)
  • router (8)
  • UserUpdateRequest (11-14)
  • BulkUserRequest (17-19)
  • get_user_profile (23-37)
  • update_user_profile (41-57)
  • bulk_user_action (61-79)
app/auth/dependencies.py (1)
  • get_current_user (10-32)

Comment on lines +31 to +32
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
Copy link

Choose a reason for hiding this comment

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

Missing Custom Claims Null Safety

Direct dictionary access on custom_claims without null checking causes KeyError when claims are empty or missing. Service crashes with 500 error when users lack first_name/last_name claims, making authentication service unreliable for users without complete profile data.

            "first_name": custom_claims.get("first_name", "") if custom_claims else "",
            "last_name": custom_claims.get("last_name", "") if custom_claims else "",
Commitable Suggestion
Suggested change
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
"first_name": custom_claims.get("first_name", "") if custom_claims else "",
"last_name": custom_claims.get("last_name", "") if custom_claims else "",
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • SRE-Error-Handling
Context References
  • get_user_profile function - no defensive checks for missing custom claims in app/auth/user_management.py

Comment on lines +60 to +67
@router.post("/bulk-action")
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
Copy link

Choose a reason for hiding this comment

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

Authorization Race Condition

Authorization check occurs after function execution begins rather than using dependency injection. This creates a race condition where unauthorized users can trigger function execution before authorization validation, potentially allowing timing attacks or resource consumption before being blocked.

@router.post("/bulk-action")
async def bulk_user_action(
    request: BulkUserRequest,
    current_user: Dict[str, Any] = Depends(require_admin)
):
    """Perform bulk actions on multiple users (admin only)"""
Commitable Suggestion
Suggested change
@router.post("/bulk-action")
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
@router.post("/bulk-action")
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(require_admin)
):
"""Perform bulk actions on multiple users (admin only)"""
Standards
  • CWE-862
  • OWASP-A01
  • NIST-SSDF-PW.1
Context References
  • require_admin dependency exists but not used - should replace inline check in app/auth/dependencies.py

async def get_user_profile(current_user: Dict[str, Any] = Depends(get_current_user)):
"""Get detailed user profile information"""
try:
user_record = auth.get_user(current_user["id"])
Copy link

Choose a reason for hiding this comment

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

Suggestion: The current user dict coming from the authentication dependency exposes a uid field (not id), so indexing current_user["id"] will raise a KeyError at runtime and prevent the profile from being fetched; use current_user["uid"] instead to look up the Firebase user. [logic error]

Severity Level: Minor ⚠️

Suggested change
user_record = auth.get_user(current_user["id"])
user_record = auth.get_user(current_user["uid"])
Why it matters? ⭐

The dependency get_current_user returns the dict produced by firebase_auth.verify_token, which contains "uid" (see app/auth/firebase_auth.py: lines returning {"uid": ...}) and does not provide an "id" key. Indexing current_user["id"] will raise a KeyError at runtime. Changing to current_user["uid"] fixes a real runtime bug.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** app/auth/user_management.py
**Line:** 26:26
**Comment:**
	*Logic Error: The current user dict coming from the authentication dependency exposes a `uid` field (not `id`), so indexing `current_user["id"]` will raise a KeyError at runtime and prevent the profile from being fetched; use `current_user["uid"]` instead to look up the Firebase user.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +27 to +32
custom_claims = auth.get_custom_user_claims(user_record.uid)
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
Copy link

Choose a reason for hiding this comment

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

Suggestion: auth.get_custom_user_claims can return None or omit first_name/last_name for some users, and indexing like custom_claims["first_name"] will raise a KeyError; normalizing to an empty dict and using .get(..., "") avoids runtime failures for users without these claims. [possible bug]

Severity Level: Critical 🚨

Suggested change
custom_claims = auth.get_custom_user_claims(user_record.uid)
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
custom_claims = auth.get_custom_user_claims(user_record.uid) or {}
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims.get("first_name", ""),
"last_name": custom_claims.get("last_name", ""),
Why it matters? ⭐

firebase_admin.auth.get_custom_user_claims can return None or omit keys. The current code indexes custom_claims["first_name"] / ["last_name"] directly, which will raise a KeyError (or TypeError if custom_claims is None). Normalizing to {} and using .get(..., "") prevents runtime errors for users without those claims. This is a defensive fix that prevents 500s for legitimate users.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** app/auth/user_management.py
**Line:** 27:32
**Comment:**
	*Possible Bug: `auth.get_custom_user_claims` can return `None` or omit `first_name`/`last_name` for some users, and indexing like `custom_claims["first_name"]` will raise a KeyError; normalizing to an empty dict and using `.get(..., "")` avoids runtime failures for users without these claims.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

):
"""Update current user's profile information"""
try:
user_record = auth.get_user(current_user["id"])
Copy link

Choose a reason for hiding this comment

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

Suggestion: As with the profile GET handler, the current user data produced by the auth dependency contains a uid field rather than id, so using current_user["id"] here will raise a KeyError and break profile updates; it should reference current_user["uid"] instead. [logic error]

Severity Level: Minor ⚠️

Suggested change
user_record = auth.get_user(current_user["id"])
user_record = auth.get_user(current_user["uid"])
Why it matters? ⭐

Same root cause as suggestion 1: get_current_user returns a dict with "uid" (from verify_token) not "id". The update_user_profile function uses current_user["id"] and will raise KeyError. Switching to current_user["uid"] resolves a real logic/runtime error.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** app/auth/user_management.py
**Line:** 47:47
**Comment:**
	*Logic Error: As with the profile GET handler, the current user data produced by the auth dependency contains a `uid` field rather than `id`, so using `current_user["id"]` here will raise a KeyError and break profile updates; it should reference `current_user["uid"]` instead.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
results = []
Copy link

Choose a reason for hiding this comment

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

Suggestion: The bulk action endpoint silently accepts any action string and just returns an empty results list when the action is unsupported, which is misleading and hides misuse of the API; it should explicitly reject invalid actions with a 400 error before processing. [logic error]

Severity Level: Minor ⚠️

Suggested change
results = []
if request.action not in {"disable", "delete"}:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Unsupported bulk action"
)
Why it matters? ⭐

As written, unsupported actions are silently ignored and callers get an empty/partial results list which is misleading. Validating request.action early and returning HTTP 400 for unsupported actions is a correctness improvement and prevents clients from thinking the call succeeded when it did nothing.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** app/auth/user_management.py
**Line:** 68:68
**Comment:**
	*Logic Error: The bulk action endpoint silently accepts any `action` string and just returns an empty `results` list when the action is unsupported, which is misleading and hides misuse of the API; it should explicitly reject invalid actions with a 400 error before processing.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +69 to +78
for user_id in request.user_ids:
try:
if request.action == "disable":
auth.update_user(user_id, disabled=True)
results.append({"user_id": user_id, "status": "disabled"})
elif request.action == "delete":
auth.delete_user(user_id)
results.append({"user_id": user_id, "status": "deleted"})
except Exception as e:
results.append({"user_id": user_id, "status": "error", "message": str(e)})
Copy link

Choose a reason for hiding this comment

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

Sequential User Processing Bottleneck

Sequential Firebase auth operations in loop create O(n) blocking I/O pattern. Each user operation waits for Firebase API response before processing next user. Bulk operations with hundreds of users will cause significant response delays and poor scalability.

    import asyncio
    
    async def process_user(user_id: str, action: str):
        try:
            if action == "disable":
                auth.update_user(user_id, disabled=True)
                return {"user_id": user_id, "status": "disabled"}
            elif action == "delete":
                auth.delete_user(user_id)
                return {"user_id": user_id, "status": "deleted"}
        except Exception as e:
            return {"user_id": user_id, "status": "error", "message": str(e)}
    
    tasks = [process_user(user_id, request.action) for user_id in request.user_ids]
    results = await asyncio.gather(*tasks, return_exceptions=True)
Commitable Suggestion
Suggested change
for user_id in request.user_ids:
try:
if request.action == "disable":
auth.update_user(user_id, disabled=True)
results.append({"user_id": user_id, "status": "disabled"})
elif request.action == "delete":
auth.delete_user(user_id)
results.append({"user_id": user_id, "status": "deleted"})
except Exception as e:
results.append({"user_id": user_id, "status": "error", "message": str(e)})
import asyncio
async def process_user(user_id: str, action: str):
try:
if action == "disable":
auth.update_user(user_id, disabled=True)
return {"user_id": user_id, "status": "disabled"}
elif action == "delete":
auth.delete_user(user_id)
return {"user_id": user_id, "status": "deleted"}
except Exception as e:
return {"user_id": user_id, "status": "error", "message": str(e)}
tasks = [process_user(user_id, request.action) for user_id in request.user_ids]
results = await asyncio.gather(*tasks, return_exceptions=True)
Standards
  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Async-Processing
  • Algorithmic-Complexity-Linear-Optimization

Comment on lines +50 to +54
auth.set_custom_user_claims(user_record.uid, {
"first_name": update_data.first_name,
"last_name": update_data.last_name,
"role": current_user.get("role", "user")
})
Copy link

Choose a reason for hiding this comment

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

Custom Claims Data Loss

Complete custom claims replacement overwrites existing claims beyond first_name, last_name, and role. User loses additional custom claims causing feature degradation. Authorization and personalization data permanently lost during profile updates.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Postconditions
Context References
  • update_user_profile function - set_custom_user_claims replaces all claims in app/auth/user_management.py

Comment on lines +71 to +76
if request.action == "disable":
auth.update_user(user_id, disabled=True)
results.append({"user_id": user_id, "status": "disabled"})
elif request.action == "delete":
auth.delete_user(user_id)
results.append({"user_id": user_id, "status": "deleted"})
Copy link

Choose a reason for hiding this comment

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

Missing Action Type Validation

Bulk action logic only handles 'disable' and 'delete' actions but lacks validation for invalid action types. Unrecognized actions silently skip processing without error indication, creating incomplete business rule enforcement.

Standards
  • Business-Rule-Input-Validation
  • Logic-Verification-Complete-Coverage

@codeant-ai
Copy link

codeant-ai bot commented Dec 24, 2025

CodeAnt AI finished reviewing your PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
app/auth/user_management.py (4)

3-3: Imported require_admin is not used.

The require_admin dependency is imported but never utilized. The /bulk-action endpoint manually checks the admin role at lines 66-67 instead of using this dependency.


11-14: Consider making fields optional for partial updates.

All fields are currently required, forcing clients to provide email, first_name, and last_name even when updating only one field.

🔎 Proposed refactor to support optional fields
 class UserUpdateRequest(BaseModel):
-    email: EmailStr
-    first_name: str
-    last_name: str
+    email: EmailStr | None = None
+    first_name: str | None = None
+    last_name: str | None = None

You'll also need to update the update_user_profile function to handle optional fields conditionally.


36-37: Improve exception handling specificity.

Catching bare Exception masks all errors, including programming errors. Additionally, re-raising without from e loses the original traceback.

🔎 Proposed improvement
-    except Exception as e:
-        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
+    except auth.UserNotFoundError as e:
+        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") from e
+    except Exception as e:
+        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) from e

56-57: Improve exception handling specificity.

Catching bare Exception and re-raising without from e loses context. Consider handling specific Firebase exceptions.

🔎 Proposed improvement
-    except Exception as e:
-        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
+    except auth.UserNotFoundError as e:
+        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") from e
+    except auth.EmailAlreadyExistsError as e:
+        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already in use") from e
+    except Exception as e:
+        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) from e
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f88f90 and bd8ed3e.

📒 Files selected for processing (1)
  • app/auth/user_management.py
🧰 Additional context used
🧬 Code graph analysis (1)
app/auth/user_management.py (1)
app/auth/dependencies.py (1)
  • get_current_user (10-32)
🪛 Ruff (0.14.10)
app/auth/user_management.py

23-23: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


36-36: Do not catch blind exception: Exception

(BLE001)


37-37: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


43-43: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


55-55: Consider moving this statement to an else block

(TRY300)


56-56: Do not catch blind exception: Exception

(BLE001)


57-57: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


63-63: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


77-77: Do not catch blind exception: Exception

(BLE001)

Comment on lines +17 to +19
class BulkUserRequest(BaseModel):
user_ids: List[str]
action: str
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the action field to prevent unintended operations.

The action field accepts any string value without validation, which is a security risk. Only "disable" and "delete" are handled in the code, but invalid actions would silently succeed without performing any operation.

🔎 Proposed fix using Literal for action validation
+from typing import Dict, Any, List, Literal
-from typing import Dict, Any, List
 
 class BulkUserRequest(BaseModel):
     user_ids: List[str]
-    action: str
+    action: Literal["disable", "delete"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class BulkUserRequest(BaseModel):
user_ids: List[str]
action: str
from typing import Dict, Any, List, Literal
class BulkUserRequest(BaseModel):
user_ids: List[str]
action: Literal["disable", "delete"]
🤖 Prompt for AI Agents
In app/auth/user_management.py around lines 17 to 19, the BulkUserRequest.model
currently allows any string for action which can silently accept unsupported
operations; change the type of action to a constrained type (e.g.,
typing.Literal["disable","delete"] or a small Enum) so Pydantic will validate
input and reject invalid actions, update the import (from typing import Literal
or add Enum) and adjust any callers/tests if needed to use the new typed values;
ensure validation errors are surfaced to callers rather than allowing no-op
behavior.

Comment on lines +25 to +35
try:
user_record = auth.get_user(current_user["id"])
custom_claims = auth.get_custom_user_claims(user_record.uid)
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
"is_active": not user_record.disabled,
"created_at": str(user_record.user_metadata.creation_timestamp)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix incorrect Firebase Admin SDK usage and handle missing custom claims.

Two critical issues:

  1. Line 27: auth.get_custom_user_claims() does not exist in the Firebase Admin SDK. Custom claims are accessed via user_record.custom_claims.

  2. Lines 31-32: Direct dictionary access will raise KeyError if custom claims are None or if first_name/last_name keys don't exist.

🔎 Proposed fix
     try:
         user_record = auth.get_user(current_user["id"])
-        custom_claims = auth.get_custom_user_claims(user_record.uid)
+        custom_claims = user_record.custom_claims or {}
         return {
             "id": user_record.uid,
             "email": user_record.email,
-            "first_name": custom_claims["first_name"],
-            "last_name": custom_claims["last_name"],
+            "first_name": custom_claims.get("first_name"),
+            "last_name": custom_claims.get("last_name"),
             "is_active": not user_record.disabled,
             "created_at": str(user_record.user_metadata.creation_timestamp)
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
user_record = auth.get_user(current_user["id"])
custom_claims = auth.get_custom_user_claims(user_record.uid)
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims["first_name"],
"last_name": custom_claims["last_name"],
"is_active": not user_record.disabled,
"created_at": str(user_record.user_metadata.creation_timestamp)
}
try:
user_record = auth.get_user(current_user["id"])
custom_claims = user_record.custom_claims or {}
return {
"id": user_record.uid,
"email": user_record.email,
"first_name": custom_claims.get("first_name"),
"last_name": custom_claims.get("last_name"),
"is_active": not user_record.disabled,
"created_at": str(user_record.user_metadata.creation_timestamp)
}
🤖 Prompt for AI Agents
In app/auth/user_management.py around lines 25 to 35, replace the incorrect call
to auth.get_custom_user_claims(...) with reading user_record.custom_claims and
guard against None or missing keys; after fetching user_record =
auth.get_user(current_user["id"]), read claims = user_record.custom_claims or {}
and use claims.get("first_name") and claims.get("last_name") (or provide safe
defaults like None or empty string) when building the return dict, so the code
never raises KeyError if custom claims are absent.

Comment on lines +46 to +54
try:
user_record = auth.get_user(current_user["id"])
if update_data.email != user_record.email:
auth.update_user(user_record.uid, email=update_data.email)
auth.set_custom_user_claims(user_record.uid, {
"first_name": update_data.first_name,
"last_name": update_data.last_name,
"role": current_user.get("role", "user")
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Risk of overwriting existing custom claims.

The set_custom_user_claims call replaces all custom claims with only first_name, last_name, and role. If other custom claims exist (e.g., permissions, metadata), they will be lost.

🔎 Proposed fix to preserve existing claims
     try:
         user_record = auth.get_user(current_user["id"])
         if update_data.email != user_record.email:
             auth.update_user(user_record.uid, email=update_data.email)
+        
+        # Preserve existing custom claims
+        existing_claims = user_record.custom_claims or {}
+        updated_claims = {
+            **existing_claims,
+            "first_name": update_data.first_name,
+            "last_name": update_data.last_name,
+            "role": current_user.get("role", "user")
+        }
-        auth.set_custom_user_claims(user_record.uid, {
-            "first_name": update_data.first_name,
-            "last_name": update_data.last_name,
-            "role": current_user.get("role", "user")
-        })
+        auth.set_custom_user_claims(user_record.uid, updated_claims)
         return {"message": "Profile updated successfully"}
🤖 Prompt for AI Agents
In app/auth/user_management.py around lines 46 to 54, the call to
set_custom_user_claims unconditionally overwrites all existing custom claims;
instead, fetch the current custom claims from user_record (or auth.get_user(...)
if not present), merge them with the new keys ("first_name", "last_name",
"role") so existing keys like "permissions" or "metadata" are preserved, and
then call set_custom_user_claims with the merged dictionary; ensure you handle
cases where user_record.custom_claims is None and validate/normalize types
before setting.

Comment on lines +60 to +67
@router.post("/bulk-action")
async def bulk_user_action(
request: BulkUserRequest,
current_user: Dict[str, Any] = Depends(get_current_user)
):
"""Perform bulk actions on multiple users (admin only)"""
if current_user.get("role") != "admin":
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use the require_admin dependency instead of manual role check.

The require_admin dependency is already imported but not used. Replace the manual role check with the dependency for consistency and better separation of concerns.

🔎 Proposed refactor
 @router.post("/bulk-action")
 async def bulk_user_action(
     request: BulkUserRequest,
-    current_user: Dict[str, Any] = Depends(get_current_user)
+    current_user: Dict[str, Any] = Depends(require_admin)
 ):
     """Perform bulk actions on multiple users (admin only)"""
-    if current_user.get("role") != "admin":
-        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
     results = []
🧰 Tools
🪛 Ruff (0.14.10)

63-63: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
In app/auth/user_management.py around lines 60 to 67, replace the manual admin
role check with the existing require_admin dependency: remove the current_user
role-if-check and change the route signature to inject current_user via
Depends(require_admin) so the dependency enforces admin access; ensure the
function parameter uses the correct type and name (e.g., current_user: Dict[str,
Any] = Depends(require_admin)) and remove the now-unneeded HTTPException branch.

Comment on lines +68 to +79
results = []
for user_id in request.user_ids:
try:
if request.action == "disable":
auth.update_user(user_id, disabled=True)
results.append({"user_id": user_id, "status": "disabled"})
elif request.action == "delete":
auth.delete_user(user_id)
results.append({"user_id": user_id, "status": "deleted"})
except Exception as e:
results.append({"user_id": user_id, "status": "error", "message": str(e)})
return {"results": results}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add safeguards for bulk operations.

Several concerns with the current implementation:

  1. No limit on bulk size: Processing unlimited users could cause timeouts or resource exhaustion.
  2. User deletion is permanent: No confirmation, soft-delete option, or audit trail for this destructive operation.
  3. Bare exception catching: Loses error context for debugging.
🔎 Proposed improvements

Add a limit check at the beginning:

    # Add after the docstring
    MAX_BULK_SIZE = 100
    if len(request.user_ids) > MAX_BULK_SIZE:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, 
            detail=f"Cannot process more than {MAX_BULK_SIZE} users at once"
        )

Consider more specific exception handling in the loop:

         try:
             if request.action == "disable":
                 auth.update_user(user_id, disabled=True)
                 results.append({"user_id": user_id, "status": "disabled"})
             elif request.action == "delete":
                 auth.delete_user(user_id)
                 results.append({"user_id": user_id, "status": "deleted"})
+        except auth.UserNotFoundError as e:
+            results.append({"user_id": user_id, "status": "error", "message": "User not found"})
         except Exception as e:
             results.append({"user_id": user_id, "status": "error", "message": str(e)})

For the deletion operation, consider implementing an audit log or requiring additional confirmation.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.10)

77-77: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In app/auth/user_management.py around lines 68 to 79, the bulk user operation
lacks safeguards: add a MAX_BULK_SIZE constant (e.g., 100) and check
len(request.user_ids) immediately to raise HTTPException 400 when exceeded;
replace the broad try/except with more specific exception handling (catch
auth-specific exceptions and fallback to Exception only to log full traceback)
and ensure errors are logged with context (user_id and exception) rather than
just returning str(e); change delete behavior to a safer approach by either
implementing soft-delete (marking a deleted flag), requiring an explicit
confirmation flag on the request for permanent deletes, and record audit entries
for deletes (user_id, actor, action, timestamp) before performing deletion;
ensure the response still collects per-user results but that destructive actions
enforce confirmation/audit and that failures are logged with full error context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants