Skip to content

Conversation

@qdaxb
Copy link
Contributor

@qdaxb qdaxb commented Dec 9, 2025

Summary

  • Add admin impersonation feature allowing administrators to impersonate users for troubleshooting
  • Implements a secure authorization workflow requiring explicit user consent
  • Includes full audit logging of all actions during impersonation sessions

Changes

Backend

  • Models: Add ImpersonationRequest and ImpersonationAuditLog tables
  • Schemas: Add Pydantic schemas for impersonation API requests/responses
  • Service: Add ImpersonationService for request lifecycle management
  • Security: Extend JWT handling to support impersonation tokens with additional claims
  • API Routes:
    • Admin endpoints: create request, list requests, cancel, start session, exit, audit logs
    • Public endpoints: get confirm info, approve request, reject request
  • Migration: Add Alembic migration for new database tables

Frontend

  • Types: Add TypeScript types for impersonation feature
  • API Client: Add impersonation API client methods
  • Components:
    • ImpersonationBanner: Shows warning banner during active impersonation
    • ImpersonateDialog: Dialog for creating impersonation requests
  • UserContext: Extended with impersonation state and exit function
  • UserList: Added impersonate button to user management
  • Confirmation Page: /impersonate/confirm/[token] for user authorization
  • i18n: Added English and Chinese translations

Security Features

  1. Authorization Required: Target user must explicitly approve the request
  2. Time-Limited Links: Authorization links expire after 30 minutes
  3. Session Expiry: Impersonation sessions auto-expire after 24 hours
  4. Audit Trail: All API actions during impersonation are logged
  5. Visual Indicator: Clear warning banner shown during active impersonation

Test Plan

  • Admin can create impersonation request for any active user
  • Confirmation link is generated and can be copied
  • Target user receives link and can view request details
  • Target user can approve or reject the request
  • Admin can start session after approval
  • Impersonation banner appears during active session
  • Admin can exit impersonation and return to their account
  • Expired requests are handled correctly
  • Audit logs are recorded for all actions during impersonation

Summary by CodeRabbit

Release Notes

  • New Features
    • Impersonation request workflow enabling temporary user account access with token-based confirmation
    • Automatic session expiry with real-time countdown timers
    • Audit logs tracking all impersonation activities
    • Admin interface for managing impersonation requests and sessions
    • Impersonation status banner with exit controls during active sessions

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

Add a new admin feature allowing administrators to impersonate users
for troubleshooting purposes. The feature requires explicit user
authorization before the impersonation session can begin.

Backend changes:
- Add ImpersonationRequest and ImpersonationAuditLog models
- Add Pydantic schemas for impersonation API
- Add impersonation service with request lifecycle management
- Extend security.py with impersonation JWT token support
- Add admin API routes for managing impersonation requests
- Add public confirmation API routes for user authorization
- Add Alembic migration for new tables

Frontend changes:
- Add TypeScript types for impersonation
- Add impersonation API client
- Add ImpersonationBanner component for active session indicator
- Extend UserContext with impersonation state management
- Add ImpersonateDialog component for creating requests
- Add impersonate button to UserList component
- Add user confirmation page at /impersonate/confirm/[token]
- Add i18n translations (en/zh-CN)

Security features:
- User must explicitly approve impersonation requests
- Authorization links expire after 30 minutes
- Sessions expire after 24 hours
- All impersonation actions are logged for audit
@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Walkthrough

This PR implements a complete impersonation feature enabling admins to temporarily assume the identity of target users. It includes database models, API endpoints for creation/approval/session management, a service layer with validation and audit logging, token-based authentication handlers, and comprehensive frontend UI with confirmation dialogs, banners, and status tracking.

Changes

Cohort / File(s) Summary
Database & Migrations
backend/alembic/versions/add_impersonation_tables.py
Adds Alembic migration creating impersonation_requests and impersonation_audit_logs tables with foreign keys, indexes, and MySQL-specific configuration.
Backend Models
backend/app/models/impersonation.py, backend/app/models/__init__.py
Introduces SQLAlchemy models ImpersonationRequest (with admin/target user relationships, token, status, expiry) and ImpersonationAuditLog (audit trail with action, method, path, IP, user-agent). Exports both in __init__.py.
Backend Schemas
backend/app/schemas/impersonation.py
Defines Pydantic schemas: request/response DTOs, confirmation info, session start/exit payloads, audit log models, and status literals ("pending", "approved", "rejected", "expired", "used").
Backend Service
backend/app/services/impersonation_service.py
Provides ImpersonationService singleton with full workflow: create, approve, reject, cancel requests; session lifecycle; expiry/cleanup; audit logging with sensitive data redaction; and confirmation URL generation.
Backend Security
backend/app/core/security.py
Adds token lifecycle helpers: create_impersonation_token(), verify_impersonation_token(), get_current_user_with_impersonation_info(), and get_impersonation_info_from_request() for JWT-based impersonation context propagation.
Backend API Endpoints
backend/app/api/api.py, backend/app/api/endpoints/impersonation.py, backend/app/api/endpoints/impersonate_confirm.py
Registers impersonation routes; implements admin endpoints (create, list, get, cancel, start session, exit, audit logs) and public confirmation endpoints (get info, approve, reject).
Frontend Types
frontend/src/types/impersonation.ts
Defines TypeScript interfaces for requests, responses, audit logs, statuses, and JWT-derived impersonation context.
Frontend API Client
frontend/src/apis/impersonation.ts
Exports impersonationApis wrapper with methods for admin workflows, confirmation endpoints, and audit log retrieval.
Frontend Context & State
frontend/src/features/common/UserContext.tsx
Extends UserContext with impersonation state (flag, impersonator name, expiry, exit function); adds token parsing and exit session handler.
Frontend Components
frontend/src/components/common/ImpersonationBanner.tsx, frontend/src/features/admin/components/ImpersonateDialog.tsx, frontend/src/features/admin/components/UserList.tsx
Adds banner (displays impersonation status and countdown), dialog (manages request creation/approval flow), and integrates impersonation button into user list.
Frontend Pages
frontend/src/app/impersonate/confirm/[token]/page.tsx
New page for public confirmation link; displays request details, authentication check, approve/reject actions, and countdown timer.
Frontend Translations
frontend/src/i18n/locales/en/admin.json, frontend/src/i18n/locales/zh-CN/admin.json
Adds English and Chinese localization for impersonation UI strings (dialog, banner, status labels, error messages).

Sequence Diagram(s)

sequenceDiagram
    actor Admin
    participant Frontend
    participant Backend API
    participant DB
    participant Mail/Link
    participant TargetUser

    Admin->>Frontend: Click impersonate user
    Frontend->>Backend API: POST /admin/impersonation/requests (target_user_id)
    Backend API->>DB: Create ImpersonationRequest (pending, token, expiry)
    DB-->>Backend API: Request created
    Backend API-->>Frontend: {token, confirmation_url, status: pending}
    Frontend->>Frontend: Display dialog with confirmation link
    
    alt Admin Shares Link
        Frontend->>Mail/Link: Email/Copy confirmation_url
        Mail/Link->>TargetUser: Receive confirmation link
    end
    
    TargetUser->>Frontend: Open confirmation link
    Frontend->>Backend API: GET /impersonate/confirm/{token}
    Backend API->>DB: Query ImpersonationRequest
    DB-->>Backend API: Request details + remaining_seconds
    Backend API-->>Frontend: {status: pending, admin_name, remaining_seconds}
    Frontend->>Frontend: Show approval/rejection options
    
    alt TargetUser Approves
        TargetUser->>Frontend: Click Approve
        Frontend->>Backend API: POST /impersonate/confirm/{token}/approve (authenticated)
        Backend API->>DB: Update ImpersonationRequest (approved_at, session_expires_at)
        DB-->>Backend API: Updated request
        Backend API-->>Frontend: {status: approved, access_token, impersonated_user_id}
        Frontend->>Frontend: Store impersonation token
    else TargetUser Rejects
        TargetUser->>Frontend: Click Reject
        Frontend->>Backend API: POST /impersonate/confirm/{token}/reject
        Backend API->>DB: Update ImpersonationRequest (status: rejected)
        DB-->>Backend API: Updated request
        Backend API-->>Frontend: {status: rejected}
    end
Loading
sequenceDiagram
    actor Admin
    participant Frontend
    participant Backend API
    participant DB
    participant Security

    Admin->>Frontend: (In approved state, click Start Session)
    Frontend->>Backend API: POST /admin/impersonation/sessions (request_id)
    Backend API->>DB: Fetch ImpersonationRequest (verify approved status)
    DB-->>Backend API: Request details
    
    alt Session Valid
        Backend API->>Security: create_impersonation_token(admin_user, target_user, expiry)
        Security-->>Backend API: JWT with impersonation metadata
        Backend API->>DB: Update ImpersonationRequest (status: used)
        Backend API->>DB: Log audit event (action: session_started)
        DB-->>Backend API: Logged
        Backend API-->>Frontend: {access_token, impersonated_user_id, session_expires_at}
        Frontend->>Frontend: Store token in localStorage
        Frontend->>Frontend: Update UserContext (isImpersonating: true)
        Frontend->>Frontend: Banner appears with countdown
        Frontend->>Frontend: Page reloads/redirects
    else Session Expired
        Backend API-->>Frontend: Error (session expired)
    end
    
    Admin->>Frontend: (While impersonating, click Exit)
    Frontend->>Backend API: POST /admin/impersonation/sessions/exit
    Backend API->>Security: verify_impersonation_token(token)
    Security-->>Backend API: Impersonation metadata (admin_user_id)
    Backend API->>Security: create_token(admin_user)
    Security-->>Backend API: New admin JWT
    Backend API->>DB: Log audit event (action: session_exited)
    Backend API-->>Frontend: {access_token (admin token), message}
    Frontend->>Frontend: Clear impersonation token
    Frontend->>Frontend: Update UserContext (isImpersonating: false)
    Frontend->>Frontend: Store admin token, navigate to /admin
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Security token handling (backend/app/core/security.py): Verify JWT payload structure, expiration validation, and that impersonation claims cannot be forged or replayed.
  • Service layer logic (backend/app/services/impersonation_service.py): Validate expiry checks, approval state transitions, sensitive data redaction strategy, and audit log sanitization correctness.
  • API endpoint access control (backend/app/api/endpoints/impersonation.py, impersonate_confirm.py): Ensure admin-only endpoints properly check authorization and public confirmation endpoints have appropriate rate-limiting/token validation.
  • Frontend token persistence and parsing (frontend/src/features/common/UserContext.tsx): Verify safe JWT decoding, impersonation state reset on exit, and proper cleanup of tokens.
  • Database migration (backend/alembic/versions/add_impersonation_tables.py): Confirm foreign key constraints, index efficiency, and cross-database compatibility (SQLite/MySQL).

Possibly related PRs

  • Wegent#268: Modifies backend/app/api/api.py to register new APIRouter endpoints (similar pattern for adding impersonation routes).

Suggested reviewers

  • feifei325

Poem

🐰✨ A feature hops in with tokens so bright,
Admins now masquerade, with banners in sight!
Confirm and approve, with countdown so fair,
Exit with grace—back to roots, if you dare! 🎭

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(admin): add user impersonation feature' directly and clearly describes the main change—adding a user impersonation capability to the admin panel.
Docstring Coverage ✅ Passed Docstring coverage is 91.18% which is sufficient. The required threshold is 80.00%.
✨ 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 wegent/admin-impersonate-user-login

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.

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/i18n/locales/en/admin.json (1)

15-22: Missing close translation key in common section.

The ImpersonateDialog.tsx component references t('common.close') at line 292, but this key is not defined in the common section. Only cancel, create, save, delete, reset, and loading are present.

Add the missing translation:

   "common": {
     "cancel": "Cancel",
+    "close": "Close",
     "create": "Create",
     "save": "Save",
     "delete": "Delete",
     "reset": "Reset",
     "loading": "Loading..."
   },
🧹 Nitpick comments (14)
backend/alembic/versions/add_impersonation_tables.py (1)

65-67: Redundant indexes on primary key columns.

The ix_impersonation_requests_id and ix_impersonation_audit_logs_id indexes are unnecessary since the id columns are already primary keys, which automatically creates a unique index.

Consider removing these redundant index operations:

-    # Create indexes for impersonation_requests
-    op.create_index(
-        "ix_impersonation_requests_id", "impersonation_requests", ["id"], unique=False
-    )
     op.create_index(
         "ix_impersonation_requests_admin_user_id",
-    # Create indexes for impersonation_audit_logs
-    op.create_index(
-        "ix_impersonation_audit_logs_id", "impersonation_audit_logs", ["id"], unique=False
-    )
     op.create_index(
         "ix_impersonation_audit_logs_request_id",

And corresponding removals in the downgrade() function.

Also applies to: 125-127

backend/app/core/security.py (3)

216-249: Ensure timezone-aware datetime for token expiration.

The session_expires_at parameter is used directly as the JWT exp claim. If the caller passes a naive datetime, JWT validation may behave unexpectedly depending on the server's timezone.

Consider ensuring timezone awareness:

 def create_impersonation_token(
     target_user: User,
     admin_user: User,
     impersonation_request_id: int,
     session_expires_at: datetime,
 ) -> str:
     from datetime import timezone

+    # Ensure timezone-aware expiration
+    if session_expires_at.tzinfo is None:
+        session_expires_at = session_expires_at.replace(tzinfo=timezone.utc)
+
     to_encode = {
         "sub": target_user.user_name,

289-290: Preserve exception chain for better debugging.

Per static analysis hint, use raise ... from to maintain the exception chain.

-    except JWTError:
-        raise credentials_exception
+    except JWTError as err:
+        raise credentials_exception from err

336-341: Preserve exception chain here as well.

-    except JWTError:
-        raise HTTPException(
+    except JWTError as err:
+        raise HTTPException(
             status_code=status.HTTP_401_UNAUTHORIZED,
             detail="Could not validate credentials",
             headers={"WWW-Authenticate": "Bearer"},
-        )
+        ) from err
backend/app/models/__init__.py (1)

18-26: Consider sorting __all__ alphabetically.

Per static analysis and isort conventions, the __all__ list should be sorted.

 __all__ = [
+    "ImpersonationAuditLog",
+    "ImpersonationRequest",
+    "Kind",
+    "SharedTeam",
+    "SkillBinary",
+    "Subtask",
     "User",
-    "Kind",
-    "Subtask",
-    "SharedTeam",
-    "SkillBinary",
-    "ImpersonationRequest",
-    "ImpersonationAuditLog",
 ]
frontend/src/features/admin/components/ImpersonateDialog.tsx (2)

82-89: Potential infinite re-creation loop due to createRequest dependency.

The createRequest callback is recreated whenever targetUser, toast, t, or onClose changes. Since it's in the dependency array of this useEffect, and createRequest sets state (setRequest), this could cause unintended behavior if any of these dependencies change while the dialog is open.

Consider using a ref to track if the request has already been created for the current dialog session:

+  const requestCreatedRef = useRef(false);
+
   // Create request when dialog opens
   useEffect(() => {
     if (isOpen && targetUser) {
+      if (requestCreatedRef.current) return;
+      requestCreatedRef.current = true;
       createRequest();
     } else {
       setRequest(null);
       setCopied(false);
+      requestCreatedRef.current = false;
     }
-  }, [isOpen, targetUser, createRequest]);
+  }, [isOpen, targetUser]);

165-178: Consider showing error toast if cancel fails.

The cancel handler silently logs errors but still closes the dialog. While this UX is acceptable, users might want feedback if the cancellation didn't actually succeed on the server.

     try {
       await impersonationApis.cancelRequest(request.id);
       toast({ title: t('impersonation.request_cancelled') });
     } catch (error) {
       console.error('Failed to cancel request:', error);
+      toast({
+        variant: 'destructive',
+        title: 'Failed to cancel request',
+      });
     }
     onClose();
backend/app/models/impersonation.py (1)

52-83: Consider adding direct user relationships for query efficiency.

The ImpersonationAuditLog model stores admin_user_id and target_user_id but only has a relationship to ImpersonationRequest. In the endpoint code (lines 259-261 in impersonation.py), user names are fetched via log.impersonation_request.admin_user.user_name, which requires traversing multiple relationships and could cause N+1 query issues when listing logs.

Consider adding direct relationships:

     # Relationships
     impersonation_request = relationship(
         "ImpersonationRequest", backref="audit_logs"
     )
+    admin_user = relationship(
+        "User", foreign_keys=[admin_user_id]
+    )
+    target_user = relationship(
+        "User", foreign_keys=[target_user_id]
+    )
backend/app/api/endpoints/impersonation.py (1)

252-270: Potential N+1 query issue when building audit log responses.

Each log item accesses log.impersonation_request.admin_user.user_name and log.impersonation_request.target_user.user_name, which can trigger additional queries per log entry. Consider eager loading these relationships in the service layer or adding direct relationships to the ImpersonationAuditLog model.

In the service layer's list_audit_logs method, add eager loading:

from sqlalchemy.orm import joinedload

query = db.query(ImpersonationAuditLog).options(
    joinedload(ImpersonationAuditLog.impersonation_request)
    .joinedload(ImpersonationRequest.admin_user),
    joinedload(ImpersonationAuditLog.impersonation_request)
    .joinedload(ImpersonationRequest.target_user),
)
backend/app/schemas/impersonation.py (1)

26-44: Consider excluding token from response in certain contexts.

The ImpersonationRequestResponse includes the full token field. While this is needed for generating the confirmation URL, consider whether the raw token should be exposed in all API responses, as it could potentially be logged or leaked. The confirmation URL itself is sufficient for the user flow.

If you want to limit token exposure, you could create a separate response schema without the token field for list operations, or ensure the token is only returned during creation.

backend/app/services/impersonation_service.py (4)

29-42: Add ClassVar annotation and handle invalid environment variable values.

Two issues with class attributes:

  1. SENSITIVE_FIELDS is a mutable class attribute that should be annotated with ClassVar (per static analysis hint RUF012).
  2. int(os.getenv(...)) will raise ValueError at import time if the environment variable is set to a non-numeric string.
+from typing import Any, ClassVar, Dict, List, Optional, Tuple
-from typing import Any, Dict, List, Optional, Tuple

 class ImpersonationService:
     """Service for managing impersonation requests and sessions."""

     # Configuration with environment variable support
-    REQUEST_EXPIRY_MINUTES = int(os.getenv("IMPERSONATION_REQUEST_EXPIRY_MINUTES", "30"))
-    SESSION_EXPIRY_HOURS = int(os.getenv("IMPERSONATION_SESSION_EXPIRY_HOURS", "24"))
-    AUDIT_RETENTION_DAYS = int(os.getenv("IMPERSONATION_AUDIT_RETENTION_DAYS", "30"))
+    REQUEST_EXPIRY_MINUTES: ClassVar[int] = int(
+        os.getenv("IMPERSONATION_REQUEST_EXPIRY_MINUTES", "") or "30"
+    )
+    SESSION_EXPIRY_HOURS: ClassVar[int] = int(
+        os.getenv("IMPERSONATION_SESSION_EXPIRY_HOURS", "") or "24"
+    )
+    AUDIT_RETENTION_DAYS: ClassVar[int] = int(
+        os.getenv("IMPERSONATION_AUDIT_RETENTION_DAYS", "") or "30"
+    )

     # Sensitive fields to redact in audit logs
-    SENSITIVE_FIELDS = [
+    SENSITIVE_FIELDS: ClassVar[List[str]] = [
         "password",

278-313: Consider adding expiration check for consistency with approve_request.

The approve_request method checks for expiration (lines 258-265) and updates status to "expired" if the request has expired. However, reject_request lacks this check, allowing rejection of already-expired requests.

         # Check request status
         if request.status != "pending":
             raise HTTPException(
                 status_code=status.HTTP_400_BAD_REQUEST,
                 detail=f"Request is already {request.status}",
             )

+        # Check expiration
+        if request.expires_at < datetime.now(timezone.utc):
+            request.status = "expired"
+            db.commit()
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail="Request has expired",
+            )
+
         # Reject the request
         request.status = "rejected"

551-562: Consider using settings for consistent configuration access.

The settings object is imported (line 20) but not used. FRONTEND_URL should come from settings for centralized configuration management rather than directly from os.getenv.

     def get_confirmation_url(self, token: str) -> str:
         """
         Generate confirmation URL for impersonation request.

         Args:
             token: Request token

         Returns:
             Confirmation URL
         """
-        frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
+        frontend_url = getattr(settings, "FRONTEND_URL", "http://localhost:3000")
         return f"{frontend_url}/impersonate/confirm/{token}"

Alternatively, add FRONTEND_URL to your settings configuration class if not already present.


162-198: Consider adding bounds validation for pagination parameters.

If page < 1 or limit <= 0 is passed, the offset calculation (page - 1) * limit could produce unexpected results (negative offset or zero/negative limit). While the API layer may validate these, defensive validation here would prevent misuse.

     def list_requests(
         self,
         db: Session,
         admin_user_id: int,
         page: int = 1,
         limit: int = 20,
         status_filter: Optional[str] = None,
     ) -> Tuple[List[ImpersonationRequest], int]:
+        page = max(1, page)
+        limit = max(1, min(limit, 100))  # Cap at reasonable maximum
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 744f5d5 and 86ec04f.

📒 Files selected for processing (18)
  • backend/alembic/versions/add_impersonation_tables.py (1 hunks)
  • backend/app/api/api.py (2 hunks)
  • backend/app/api/endpoints/impersonate_confirm.py (1 hunks)
  • backend/app/api/endpoints/impersonation.py (1 hunks)
  • backend/app/core/security.py (2 hunks)
  • backend/app/models/__init__.py (2 hunks)
  • backend/app/models/impersonation.py (1 hunks)
  • backend/app/schemas/impersonation.py (1 hunks)
  • backend/app/services/impersonation_service.py (1 hunks)
  • frontend/src/apis/impersonation.ts (1 hunks)
  • frontend/src/app/impersonate/confirm/[token]/page.tsx (1 hunks)
  • frontend/src/components/common/ImpersonationBanner.tsx (1 hunks)
  • frontend/src/features/admin/components/ImpersonateDialog.tsx (1 hunks)
  • frontend/src/features/admin/components/UserList.tsx (5 hunks)
  • frontend/src/features/common/UserContext.tsx (4 hunks)
  • frontend/src/i18n/locales/en/admin.json (1 hunks)
  • frontend/src/i18n/locales/zh-CN/admin.json (1 hunks)
  • frontend/src/types/impersonation.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
frontend/src/i18n/**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Frontend internationalization (i18n) files must be placed in src/i18n/ with language subdirectories: en, zh-CN.

Files:

  • frontend/src/i18n/locales/zh-CN/admin.json
  • frontend/src/i18n/locales/en/admin.json
**/*.{py,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

All code comments MUST be written in English, including inline comments, block comments, docstrings, TODO/FIXME annotations, and type hints descriptions.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • backend/app/models/__init__.py
  • frontend/src/app/impersonate/confirm/[token]/page.tsx
  • backend/app/api/endpoints/impersonate_confirm.py
  • backend/app/models/impersonation.py
  • backend/app/core/security.py
  • backend/app/api/api.py
  • backend/alembic/versions/add_impersonation_tables.py
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
  • frontend/src/apis/impersonation.ts
  • frontend/src/types/impersonation.ts
  • backend/app/api/endpoints/impersonation.py
  • backend/app/schemas/impersonation.py
  • backend/app/services/impersonation_service.py
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: TypeScript code must use strict mode, functional components, Prettier for formatting, ESLint for linting, single quotes, and no semicolons.
Run npm run format && npm run lint to format and lint TypeScript/React code.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/app/impersonate/confirm/[token]/page.tsx
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
  • frontend/src/apis/impersonation.ts
  • frontend/src/types/impersonation.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use const over let, never use var in TypeScript.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/app/impersonate/confirm/[token]/page.tsx
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
  • frontend/src/apis/impersonation.ts
  • frontend/src/types/impersonation.ts
frontend/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

React component names must use PascalCase, and component files must use kebab-case.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/**/*.{ts,tsx}: Use Tailwind CSS classes for frontend styling following spacing conventions: p-2 (8px), p-4 (16px), p-6 (24px), gap-3 (12px), with border radius rounded-2xl (16px), rounded-lg (12px), rounded-md (6px).
Frontend typography should use: H1 text-xl font-semibold, H2 text-lg font-semibold, Body text-sm.
Import and use shadcn/ui components from @/components/ui/ for Button, Card, Input, Dialog, Drawer, Select, SearchableSelect, Switch, Checkbox, RadioGroup, Badge, Tag, Alert, Toast, Tooltip, Form, Transfer, Progress, and Spinner.
Frontend PDF export functionality must use ExportPdfButton, ExportPdfModal, and pdf-generator.ts utilities.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/app/impersonate/confirm/[token]/page.tsx
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
  • frontend/src/apis/impersonation.ts
  • frontend/src/types/impersonation.ts
frontend/src/components/common/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Frontend custom components must be placed in src/components/common/.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
frontend/**/*.{ts,tsx,.env*}

📄 CodeRabbit inference engine (AGENTS.md)

Frontend: Only use NEXT_PUBLIC_* for client-safe environment variables.

Files:

  • frontend/src/components/common/ImpersonationBanner.tsx
  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/app/impersonate/confirm/[token]/page.tsx
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
  • frontend/src/apis/impersonation.ts
  • frontend/src/types/impersonation.ts
frontend/src/features/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/features/**/*.{ts,tsx}: Frontend feature modules must be organized in src/features/ with subdirectories for: common, layout, login, settings, tasks, theme, onboarding, admin.
Frontend Task and Team sharing must use TaskShareModal and TeamShareModal components.
Frontend Dify integration must use DifyAppSelector and DifyParamsForm components.

Files:

  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/features/admin/components/UserList.tsx
  • frontend/src/features/common/UserContext.tsx
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python code must follow PEP 8 style with Black formatter (line length: 88), isort for imports, and type hints required.
Run Black and isort on Python code: black . && isort .
Extract magic numbers to named constants in Python code.
Keep Python functions to a maximum of 50 lines (preferred).
Write descriptive names and docstrings for public functions and classes in Python.

Files:

  • backend/app/models/__init__.py
  • backend/app/api/endpoints/impersonate_confirm.py
  • backend/app/models/impersonation.py
  • backend/app/core/security.py
  • backend/app/api/api.py
  • backend/alembic/versions/add_impersonation_tables.py
  • backend/app/api/endpoints/impersonation.py
  • backend/app/schemas/impersonation.py
  • backend/app/services/impersonation_service.py
**/app/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run pylint and flake8 linting on Python app code: pylint app/ && flake8 app/

Files:

  • backend/app/models/__init__.py
  • backend/app/api/endpoints/impersonate_confirm.py
  • backend/app/models/impersonation.py
  • backend/app/core/security.py
  • backend/app/api/api.py
  • backend/app/api/endpoints/impersonation.py
  • backend/app/schemas/impersonation.py
  • backend/app/services/impersonation_service.py
backend/app/models/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Create SQLAlchemy models in app/models/ for database tables, then run alembic revision --autogenerate -m 'description' to create migration.

Files:

  • backend/app/models/__init__.py
  • backend/app/models/impersonation.py
backend/app/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend: Encrypt Git tokens and API keys using AES-256-CBC.

Files:

  • backend/app/models/__init__.py
  • backend/app/api/endpoints/impersonate_confirm.py
  • backend/app/models/impersonation.py
  • backend/app/core/security.py
  • backend/app/api/api.py
  • backend/app/api/endpoints/impersonation.py
  • backend/app/schemas/impersonation.py
  • backend/app/services/impersonation_service.py
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Frontend pages and routes must be organized in src/app/ using Next.js App Router.
Frontend route groups like (tasks) wrap related pages with shared contexts (UserProvider, TaskContextProvider, ChatStreamProvider).

Files:

  • frontend/src/app/impersonate/confirm/[token]/page.tsx
backend/app/api/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend API endpoints are organized in app/api/ with route handlers for: auth, bots, models, shells, teams, tasks, chat, git, executors, dify, quota, admin.

Files:

  • backend/app/api/endpoints/impersonate_confirm.py
  • backend/app/api/api.py
  • backend/app/api/endpoints/impersonation.py
backend/alembic/versions/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

backend/alembic/versions/**/*.py: Resolve Alembic multiple heads by running: cd backend && alembic heads, then alembic merge -m 'merge heads' <head1> <head2>, then alembic upgrade head, and commit the merge migration.
Database migrations: Create with alembic revision --autogenerate -m 'description', apply with alembic upgrade head, check status with alembic current, rollback with alembic downgrade -1.

Files:

  • backend/alembic/versions/add_impersonation_tables.py
frontend/src/**/*Context.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Frontend state management must use Context API with dedicated context files in appropriate feature directories.

Files:

  • frontend/src/features/common/UserContext.tsx
frontend/src/apis/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Frontend API client modules must be placed in src/apis/ with main client in client.ts and module-specific clients.

Files:

  • frontend/src/apis/impersonation.ts
frontend/src/types/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Place TypeScript types in src/types/ directory.

Files:

  • frontend/src/types/impersonation.ts
backend/app/schemas/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

backend/app/schemas/**/*.py: Update API docs (check /api/docs endpoint) and backend/app/schemas/ when API schema changes.
Create Pydantic schema definitions in app/schemas/ for API models and CRD definitions.
Bot model binding should use bind_model in agent_config (recommended) rather than legacy modelRef.

Files:

  • backend/app/schemas/impersonation.py
backend/app/services/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Backend business logic must be extracted to app/services/ to avoid duplication and maintain high cohesion, low coupling.

Files:

  • backend/app/services/impersonation_service.py
🧠 Learnings (8)
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to frontend/src/features/**/*.{ts,tsx} : Frontend Task and Team sharing must use `TaskShareModal` and `TeamShareModal` components.

Applied to files:

  • frontend/src/features/admin/components/ImpersonateDialog.tsx
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to frontend/src/**/*.{ts,tsx} : Import and use shadcn/ui components from `@/components/ui/` for Button, Card, Input, Dialog, Drawer, Select, SearchableSelect, Switch, Checkbox, RadioGroup, Badge, Tag, Alert, Toast, Tooltip, Form, Transfer, Progress, and Spinner.

Applied to files:

  • frontend/src/features/admin/components/ImpersonateDialog.tsx
  • frontend/src/features/admin/components/UserList.tsx
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to backend/app/api/**/*.py : Backend API endpoints are organized in `app/api/` with route handlers for: auth, bots, models, shells, teams, tasks, chat, git, executors, dify, quota, admin.

Applied to files:

  • backend/app/api/api.py
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: User role-based access control: admin users have full access, user role is default, API admin endpoints require `get_admin_user` dependency.

Applied to files:

  • backend/app/api/api.py
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to backend/alembic/versions/**/*.py : Database migrations: Create with `alembic revision --autogenerate -m 'description'`, apply with `alembic upgrade head`, check status with `alembic current`, rollback with `alembic downgrade -1`.

Applied to files:

  • backend/alembic/versions/add_impersonation_tables.py
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to frontend/src/**/*Context.{ts,tsx} : Frontend state management must use Context API with dedicated context files in appropriate feature directories.

Applied to files:

  • frontend/src/features/common/UserContext.tsx
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to frontend/src/app/**/*.{ts,tsx} : Frontend route groups like `(tasks)` wrap related pages with shared contexts (UserProvider, TaskContextProvider, ChatStreamProvider).

Applied to files:

  • frontend/src/features/common/UserContext.tsx
📚 Learning: 2025-12-08T11:05:05.848Z
Learnt from: CR
Repo: wecode-ai/Wegent PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T11:05:05.848Z
Learning: Applies to frontend/src/apis/**/*.{ts,tsx} : Frontend API client modules must be placed in `src/apis/` with main client in `client.ts` and module-specific clients.

Applied to files:

  • frontend/src/apis/impersonation.ts
🧬 Code graph analysis (10)
frontend/src/components/common/ImpersonationBanner.tsx (2)
frontend/src/features/common/UserContext.tsx (1)
  • useUser (238-238)
frontend/src/components/ui/button.tsx (1)
  • Button (55-55)
backend/app/models/__init__.py (1)
backend/app/models/impersonation.py (2)
  • ImpersonationAuditLog (52-83)
  • ImpersonationRequest (16-49)
backend/app/api/endpoints/impersonate_confirm.py (3)
backend/app/core/security.py (1)
  • get_current_user (28-50)
backend/app/schemas/impersonation.py (1)
  • ImpersonationConfirmInfo (54-63)
backend/app/services/impersonation_service.py (3)
  • get_request_by_token (137-160)
  • approve_request (228-276)
  • reject_request (278-313)
backend/app/models/impersonation.py (1)
frontend/src/types/impersonation.ts (2)
  • ImpersonationRequest (10-24)
  • ImpersonationAuditLog (62-76)
backend/app/core/security.py (3)
backend/app/models/user.py (1)
  • User (14-44)
backend/app/api/dependencies.py (1)
  • get_db (12-21)
backend/app/services/user.py (1)
  • get_user_by_name (331-351)
frontend/src/features/common/UserContext.tsx (1)
frontend/src/apis/impersonation.ts (1)
  • impersonationApis (17-115)
frontend/src/apis/impersonation.ts (2)
frontend/src/types/impersonation.ts (7)
  • ImpersonationRequestCreate (31-33)
  • ImpersonationRequest (10-24)
  • ImpersonationRequestListResponse (26-29)
  • ImpersonationStartResponse (47-53)
  • ImpersonationExitResponse (55-59)
  • ImpersonationAuditLogListResponse (78-81)
  • ImpersonationConfirmInfo (36-44)
frontend/src/apis/client.ts (1)
  • apiClient (105-105)
frontend/src/types/impersonation.ts (2)
backend/app/models/impersonation.py (2)
  • ImpersonationRequest (16-49)
  • ImpersonationAuditLog (52-83)
backend/app/schemas/impersonation.py (6)
  • ImpersonationRequestListResponse (47-51)
  • ImpersonationRequestCreate (20-23)
  • ImpersonationConfirmInfo (54-63)
  • ImpersonationStartResponse (66-73)
  • ImpersonationExitResponse (76-81)
  • ImpersonationAuditLogListResponse (106-110)
backend/app/api/endpoints/impersonation.py (3)
backend/app/core/security.py (3)
  • create_access_token (80-106)
  • create_impersonation_token (216-249)
  • get_admin_user (194-213)
backend/app/schemas/impersonation.py (7)
  • ImpersonationAuditLogListResponse (106-110)
  • ImpersonationAuditLogResponse (85-103)
  • ImpersonationExitResponse (76-81)
  • ImpersonationRequestCreate (20-23)
  • ImpersonationRequestListResponse (47-51)
  • ImpersonationRequestResponse (26-44)
  • ImpersonationStartResponse (66-73)
backend/app/services/impersonation_service.py (7)
  • create_request (44-105)
  • get_confirmation_url (551-562)
  • list_requests (162-198)
  • get_request (107-135)
  • cancel_request (200-226)
  • start_session (315-360)
  • list_audit_logs (456-502)
backend/app/schemas/impersonation.py (1)
frontend/src/types/impersonation.ts (7)
  • ImpersonationStatus (7-7)
  • ImpersonationRequestCreate (31-33)
  • ImpersonationRequestListResponse (26-29)
  • ImpersonationConfirmInfo (36-44)
  • ImpersonationStartResponse (47-53)
  • ImpersonationExitResponse (55-59)
  • ImpersonationAuditLogListResponse (78-81)
🪛 Ruff (0.14.8)
backend/app/models/__init__.py

18-26: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

backend/app/api/endpoints/impersonate_confirm.py

26-26: 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)


64-64: 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)


65-65: 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)


97-97: 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)


98-98: 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)

backend/app/core/security.py

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

(B904)


294-294: 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)


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

(TRY300)


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

(B904)

backend/app/api/endpoints/impersonation.py

65-65: 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)


66-66: 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)


91-91: 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)


92-92: 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)


120-120: 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)


121-121: 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)


140-140: 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)


141-141: 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)


159-159: 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)


160-160: 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)


181-181: Possible hardcoded password assigned to argument: "token_type"

(S106)


190-190: 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)


235-235: 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)


236-236: 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)

backend/app/schemas/impersonation.py

70-70: Possible hardcoded password assigned to: "token_type"

(S105)


80-80: Possible hardcoded password assigned to: "token_type"

(S105)

backend/app/services/impersonation_service.py

34-42: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test wegent CLI Integration
  • GitHub Check: e2e-tests
🔇 Additional comments (33)
backend/alembic/versions/add_impersonation_tables.py (1)

26-146: Migration structure looks good overall.

The migration correctly:

  • Creates tables with appropriate column types and constraints
  • Establishes foreign key relationships to users
  • Includes proper MySQL engine/charset options
  • Orders downgrade operations correctly (child table first)
backend/app/api/api.py (1)

5-14: LGTM!

The routing configuration appropriately:

  • Separates admin-only impersonation management (/admin) from the public confirmation flow (/impersonate)
  • Uses correct tags for API documentation grouping
  • Follows the project's endpoint organization pattern

Based on learnings, this aligns with the backend API organization guidelines.

Also applies to: 34-39

backend/app/core/security.py (1)

344-375: LGTM!

The get_impersonation_info_from_request function is well-designed for middleware-style usage, gracefully returning None on any errors instead of raising exceptions.

frontend/src/features/admin/components/UserList.tsx (2)

373-385: LGTM!

The impersonation button implementation:

  • Uses appropriate icon (IdentificationIcon)
  • Correctly disables for inactive users
  • Follows existing action button patterns in the component
  • Properly sets selected user state before opening dialog

674-682: Dialog cleanup is handled correctly.

The onClose handler properly resets both dialog state and selected user, preventing stale state on subsequent opens.

frontend/src/i18n/locales/zh-CN/admin.json (1)

163-201: LGTM!

The Chinese localization for impersonation is comprehensive and well-structured:

  • Complete coverage of dialog, status, banner, and error strings
  • Proper use of interpolation placeholders ({{name}})
  • Status labels align with backend enum values
frontend/src/components/common/ImpersonationBanner.tsx (3)

28-58: Potential issue: exitImpersonation should be stable or excluded from dependencies.

If exitImpersonation is not memoized with useCallback in the UserContext, this could cause the interval to reset on every context update. Additionally, calling exitImpersonation() directly in the interval callback without await may cause race conditions.

Please verify that exitImpersonation is stable (memoized) in UserContext. If not, consider:

   useEffect(() => {
     if (!isImpersonating || !impersonationExpiresAt) {
       return;
     }

+    const exitRef = exitImpersonation; // Capture current reference
+
     const updateRemainingTime = () => {
       const now = new Date();
       const expiresAt = new Date(impersonationExpiresAt);
       const diff = expiresAt.getTime() - now.getTime();

       if (diff <= 0) {
         setRemainingTime('00:00:00');
-        // Session expired, trigger exit
-        exitImpersonation();
+        // Session expired, trigger exit (fire-and-forget)
+        exitRef();
         return;
       }

Alternatively, remove exitImpersonation from the dependency array if it's intentional to only use the initial reference.


65-72: Good error handling in exit handler.

The try/finally pattern ensures isExiting is always reset regardless of whether exitImpersonation succeeds or fails.


74-107: LGTM!

The banner UI:

  • Uses appropriate warning colors (amber) that signal impersonation clearly
  • Fixed positioning ensures visibility across all pages
  • Proper responsive layout with flexbox
  • Good use of shadcn/ui Button component per guidelines
frontend/src/i18n/locales/en/admin.json (1)

163-200: Well-structured impersonation translations.

The translation keys are comprehensive and well-organized, covering all UI states (pending, approved, rejected, expired, used), dialog content, banner messages, and error handling.

frontend/src/features/admin/components/ImpersonateDialog.tsx (3)

141-163: Token storage and page reload approach is appropriate for session switching.

The implementation correctly stores the impersonation token and performs a full page reload to refresh the entire user context. This aligns with how UserContext parses impersonation info from the token.


91-97: Polling implementation looks correct.

The 5-second polling interval is appropriate for checking approval status, and the cleanup function properly clears the interval when the request status changes or component unmounts.


193-297: Clean component structure with proper shadcn/ui usage.

The dialog correctly uses shadcn/ui components (Dialog, Button, Input, Tag) as per coding guidelines, and the conditional rendering for different statuses is well-organized.

frontend/src/app/impersonate/confirm/[token]/page.tsx (3)

94-100: Authentication redirect flow is appropriate.

The redirect to login with return URL ensures users can complete the approval flow after authenticating. The backend endpoints also enforce authentication, providing defense in depth.


148-185: Status configuration is comprehensive.

All five impersonation statuses are handled with appropriate icons, colors, and labels. The configuration object pattern makes the code clean and maintainable.


287-304: Button state management prevents double submission.

Both buttons share the processing state to disable them during API calls, preventing double submissions. The implementation is correct.

backend/app/api/endpoints/impersonate_confirm.py (4)

23-58: Public GET endpoint with auto-expiration is well implemented.

The endpoint correctly handles timezone-aware datetime comparison, calculates remaining time, and automatically marks pending requests as expired when they timeout. The public accessibility is appropriate for the confirmation page UX.


61-91: Approve endpoint correctly uses session expiry time.

After approval, the endpoint returns session_expires_at as the expiry time instead of the original request expiry, which correctly reflects the 24-hour session duration. The service method guarantees session_expires_at is set upon approval.


94-115: Reject endpoint implementation is correct.

Hardcoding remaining_seconds=0 for rejected requests is semantically appropriate since the request is no longer actionable.


24-27: Ignore Ruff B008 warnings for FastAPI Depends().

The static analysis warnings about Depends() in argument defaults are false positives. This is the standard FastAPI pattern for dependency injection, which FastAPI handles specially and does not evaluate at function definition time.

frontend/src/features/common/UserContext.tsx (2)

88-109: Exit impersonation implementation handles errors appropriately.

The function correctly preserves impersonation state if the API call fails, preventing an inconsistent state where the user appears logged out but the server still considers them impersonating.


219-232: Context provider properly exposes impersonation state.

The UserContext.Provider correctly includes all new impersonation fields and the exitImpersonation function, following the established Context API pattern per coding guidelines.

backend/app/models/impersonation.py (1)

16-49: LGTM! Well-structured model with proper indexing.

The ImpersonationRequest model is well-designed with appropriate indexes on frequently queried columns (admin_user_id, target_user_id, token) and proper relationships to the User model. The token field at String(64) is sufficient for secrets.token_urlsafe(32) output.

frontend/src/apis/impersonation.ts (1)

1-115: LGTM! Well-organized API client module.

The impersonation API client follows project conventions with proper typing, clear separation of admin and public endpoints, and correct use of URLSearchParams for query parameter handling. Based on learnings, the module is correctly placed in src/apis/ as per coding guidelines.

frontend/src/types/impersonation.ts (1)

7-90: LGTM! Types are well-aligned with backend schemas.

The TypeScript interfaces correctly mirror the backend Pydantic schemas with appropriate type mappings (datetime → string for JSON serialization, nullable fields → | null). The ImpersonationInfo interface for JWT-derived state is a good addition for managing client-side impersonation context.

backend/app/api/endpoints/impersonation.py (2)

37-55: LGTM! Clean helper function for response formatting.

The _format_request_response helper provides consistent response formatting and reduces code duplication across endpoints.


65-66: Static analysis false positives - ignore B008 warnings.

The Ruff B008 warnings about Depends() in function argument defaults are false positives. This is the idiomatic FastAPI pattern for dependency injection and is explicitly designed to work this way.

Also applies to: 91-92, 120-121, 140-141, 159-160, 190-191, 235-236

backend/app/schemas/impersonation.py (3)

15-16: LGTM! Type alias for status values.

Using Literal for ImpersonationStatus provides compile-time type safety and matches the frontend TypeScript union type.


66-81: Static analysis false positive - token_type is not a password.

The Ruff S105 warnings on token_type: str = "bearer" are false positives. This is the standard OAuth2 token type identifier, not a credential or password.


113-127: LGTM! Comprehensive user info schema with impersonation state.

The UserInfoWithImpersonation schema appropriately extends user info with impersonation context, enabling the frontend to display warnings and track session state.

backend/app/services/impersonation_service.py (3)

44-105: LGTM!

The create_request method properly validates the target user, uses secure token generation with secrets.token_urlsafe(32), and handles the idempotent case of returning existing pending requests.


404-454: LGTM!

Good defensive practices: sanitizing request body to redact sensitive fields and truncating user_agent to prevent oversized data. The immediate commit ensures audit log durability.


504-549: LGTM!

The two-phase sanitization (JSON parsing with recursive redaction, regex fallback) is a reasonable approach for API request bodies. The recursive _redact_sensitive_fields properly handles nested structures.

Comment on lines +188 to +226
@router.post("/impersonate/exit", response_model=ImpersonationExitResponse)
async def exit_impersonation_session(
db: Session = Depends(get_db),
user_info: tuple = Depends(get_current_user_with_impersonation_info),
):
"""
Exit the current impersonation session and restore admin identity.
Returns a new JWT token for the original admin user.
"""
user, impersonation_info = user_info

if not impersonation_info.get("is_impersonating"):
# Not in impersonation mode, return current user's token
access_token = create_access_token(data={"sub": user.user_name})
return ImpersonationExitResponse(
access_token=access_token,
message="No active impersonation session",
)

# Get the admin user
admin_user_id = impersonation_info.get("impersonator_id")
admin_user = db.query(User).filter(User.id == admin_user_id).first()

if not admin_user:
# Fallback to current user if admin not found
access_token = create_access_token(data={"sub": user.user_name})
return ImpersonationExitResponse(
access_token=access_token,
message="Admin user not found, restored current user session",
)

# Create new token for admin user
access_token = create_access_token(data={"sub": admin_user.user_name})

return ImpersonationExitResponse(
access_token=access_token,
message=f"Successfully exited impersonation of {user.user_name}",
)
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

🧩 Analysis chain

🏁 Script executed:

find . -name "*impersonation*" -type f

Repository: wecode-ai/Wegent

Length of output: 359


🏁 Script executed:

cat -n backend/app/services/impersonation_service.py

Repository: wecode-ai/Wegent

Length of output: 20614


🏁 Script executed:

# Verify the endpoint that creates impersonation requests
rg -n "create_request\|@router.post.*impersonate" backend/app/api/endpoints/impersonation.py | head -20

Repository: wecode-ai/Wegent

Length of output: 42


🏁 Script executed:

cat -n backend/app/api/endpoints/impersonation.py

Repository: wecode-ai/Wegent

Length of output: 10902


🏁 Script executed:

# Double-check: search for any self-impersonation validation anywhere in impersonation-related files
rg -i "self.*impersonat|impersonat.*self|admin.*target|cannot.*impersonate.*self" backend/app/

Repository: wecode-ai/Wegent

Length of output: 548


Add self-impersonation prevention in the service layer.

The create_request method in impersonation_service.py validates that the target user exists and is active, and checks for duplicate pending requests. However, it lacks a check to prevent an admin from creating an impersonation request targeting themselves. Add validation at the start of create_request to ensure admin_user.id != target_user_id and raise an HTTPException with an appropriate error message if violated.

🧰 Tools
🪛 Ruff (0.14.8)

190-190: 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 backend/app/api/endpoints/impersonation.py around lines 188-226: add a
validation in the service layer method impersonation_service.create_request to
prevent self-impersonation by checking that admin_user.id != target_user_id at
the start of the method; if they are equal, raise an HTTPException (400 or 422)
with a clear message like "Cannot create impersonation request targeting
yourself." Ensure this check runs before any DB queries for duplicates or
target-user status validation.

Comment on lines +355 to +360
# Get target user
target_user = (
db.query(User).filter(User.id == request.target_user_id).first()
)

return request, target_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

Handle case where target user no longer exists.

The target_user query can return None if the user was deleted after the impersonation request was created. The return type signature promises User, but this method could return None, causing downstream errors.

         # Get target user
         target_user = (
             db.query(User).filter(User.id == request.target_user_id).first()
         )

+        if not target_user or not target_user.is_active:
+            request.status = "expired"
+            db.commit()
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail="Target user is no longer available",
+            )
+
         return request, target_user
🤖 Prompt for AI Agents
In backend/app/services/impersonation_service.py around lines 355 to 360, the
code fetches target_user but doesn't handle the case where the user was deleted
and db.query(...).first() returns None even though the method promises to return
a User; update the method to check if target_user is None and either raise a
clear domain-specific exception (e.g., NotFoundError or an ImpersonationError)
or return an explicit error result, include a brief log message with the
target_user_id for debugging, and update the function return type/signature and
any callers to reflect that it can raise or return an error instead of a User.

Comment on lines +52 to +66
} catch (error) {
toast({
variant: 'destructive',
title: 'Failed to load request',
description: (error as Error).message,
});
} finally {
setLoading(false);
}
};

if (token) {
fetchInfo();
}
}, [token, toast]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English strings should use i18n translations.

This page uses hardcoded English strings throughout (e.g., "Failed to load request", "Request approved", "Request Not Found") instead of using the useTranslation hook. This is inconsistent with other components in the PR and will cause issues for non-English users.

Add the translation hook and use translation keys:

+import { useTranslation } from '@/hooks/useTranslation';
 
 export default function ImpersonationConfirmPage() {
+  const { t } = useTranslation('impersonation'); // May need a new namespace
   // ...
   toast({
     variant: 'destructive',
-    title: 'Failed to load request',
+    title: t('errors.load_failed'),
     description: (error as Error).message,
   });

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

🤖 Prompt for AI Agents
In frontend/src/app/impersonate/confirm/[token]/page.tsx around lines 52 to 66,
the catch/toast and other UI strings are hardcoded in English; replace them with
i18n translations by importing and calling the useTranslation hook (e.g., const
{ t } = useTranslation('namespace')) at the top of the component and swap
literal strings like "Failed to load request", "Request approved", "Request Not
Found" for t('key') calls using appropriate translation keys; ensure the
translation keys are added to the locale files and include t in the useEffect
dependency array if referenced there.

Comment on lines +61 to +66
try {
// Decode JWT payload (base64)
const parts = token.split('.');
if (parts.length !== 3) return;

const payload = JSON.parse(atob(parts[1]));
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

JWT base64url decoding may fail with atob().

JWT uses base64url encoding (with - and _ characters) rather than standard base64 (with + and /). The atob() function expects standard base64 and may throw an error for tokens containing these characters.

Apply this fix to handle base64url properly:

     try {
       // Decode JWT payload (base64)
       const parts = token.split('.');
       if (parts.length !== 3) return;

-      const payload = JSON.parse(atob(parts[1]));
+      // JWT uses base64url encoding, convert to standard base64
+      const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
+      const payload = JSON.parse(atob(base64));
📝 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 {
// Decode JWT payload (base64)
const parts = token.split('.');
if (parts.length !== 3) return;
const payload = JSON.parse(atob(parts[1]));
try {
// Decode JWT payload (base64)
const parts = token.split('.');
if (parts.length !== 3) return;
// JWT uses base64url encoding, convert to standard base64
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const payload = JSON.parse(atob(base64));
🤖 Prompt for AI Agents
In frontend/src/features/common/UserContext.tsx around lines 61-66, the code
uses atob() directly on the JWT payload which can fail because JWTs are
base64url-encoded; convert the base64url string to standard base64 before
decoding by replacing '-' with '+' and '_' with '/', then pad the string with
'=' characters until length % 4 === 0, and only then call atob() and JSON.parse
on the result to safely decode the payload.

Comment on lines +63 to +65
const parts = token.split('.');
if (parts.length !== 3) return;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Early return for malformed token should reset impersonation state.

If the token exists but doesn't have exactly 3 parts (malformed JWT), the function returns early without resetting the impersonation state. This could leave stale impersonation state.

       const parts = token.split('.');
-      if (parts.length !== 3) return;
+      if (parts.length !== 3) {
+        setIsImpersonating(false);
+        setImpersonatorName(null);
+        setImpersonationExpiresAt(null);
+        return;
+      }
📝 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
const parts = token.split('.');
if (parts.length !== 3) return;
const parts = token.split('.');
if (parts.length !== 3) {
setIsImpersonating(false);
setImpersonatorName(null);
setImpersonationExpiresAt(null);
return;
}
🤖 Prompt for AI Agents
In frontend/src/features/common/UserContext.tsx around lines 63-65, the early
return when token.split('.') yields a length !== 3 leaves any existing
impersonation state intact; before returning for a malformed token, explicitly
clear/reset the impersonation state (e.g., set impersonated user to null and/or
set isImpersonating to false or dispatch the appropriate reset action) so no
stale impersonation remains, then return.

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.

2 participants