-
Notifications
You must be signed in to change notification settings - Fork 50
feat(admin): add user impersonation feature #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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
WalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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: Missingclosetranslation key incommonsection.The
ImpersonateDialog.tsxcomponent referencest('common.close')at line 292, but this key is not defined in thecommonsection. Onlycancel,create,save,delete,reset, andloadingare 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_idandix_impersonation_audit_logs_idindexes are unnecessary since theidcolumns 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_atparameter is used directly as the JWTexpclaim. 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 ... fromto 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 errbackend/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 tocreateRequestdependency.The
createRequestcallback is recreated whenevertargetUser,toast,t, oronClosechanges. Since it's in the dependency array of thisuseEffect, andcreateRequestsets 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
ImpersonationAuditLogmodel storesadmin_user_idandtarget_user_idbut only has a relationship toImpersonationRequest. In the endpoint code (lines 259-261 inimpersonation.py), user names are fetched vialog.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_nameandlog.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 theImpersonationAuditLogmodel.In the service layer's
list_audit_logsmethod, 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
ImpersonationRequestResponseincludes the fulltokenfield. 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: AddClassVarannotation and handle invalid environment variable values.Two issues with class attributes:
SENSITIVE_FIELDSis a mutable class attribute that should be annotated withClassVar(per static analysis hint RUF012).int(os.getenv(...))will raiseValueErrorat 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 withapprove_request.The
approve_requestmethod checks for expiration (lines 258-265) and updates status to "expired" if the request has expired. However,reject_requestlacks 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 usingsettingsfor consistent configuration access.The
settingsobject is imported (line 20) but not used.FRONTEND_URLshould come fromsettingsfor centralized configuration management rather than directly fromos.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_URLto yoursettingsconfiguration class if not already present.
162-198: Consider adding bounds validation for pagination parameters.If
page < 1orlimit <= 0is passed, the offset calculation(page - 1) * limitcould 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
📒 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.jsonfrontend/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.tsxfrontend/src/features/admin/components/ImpersonateDialog.tsxbackend/app/models/__init__.pyfrontend/src/app/impersonate/confirm/[token]/page.tsxbackend/app/api/endpoints/impersonate_confirm.pybackend/app/models/impersonation.pybackend/app/core/security.pybackend/app/api/api.pybackend/alembic/versions/add_impersonation_tables.pyfrontend/src/features/admin/components/UserList.tsxfrontend/src/features/common/UserContext.tsxfrontend/src/apis/impersonation.tsfrontend/src/types/impersonation.tsbackend/app/api/endpoints/impersonation.pybackend/app/schemas/impersonation.pybackend/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.
Runnpm run format && npm run lintto format and lint TypeScript/React code.
Files:
frontend/src/components/common/ImpersonationBanner.tsxfrontend/src/features/admin/components/ImpersonateDialog.tsxfrontend/src/app/impersonate/confirm/[token]/page.tsxfrontend/src/features/admin/components/UserList.tsxfrontend/src/features/common/UserContext.tsxfrontend/src/apis/impersonation.tsfrontend/src/types/impersonation.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use
constoverlet, never usevarin TypeScript.
Files:
frontend/src/components/common/ImpersonationBanner.tsxfrontend/src/features/admin/components/ImpersonateDialog.tsxfrontend/src/app/impersonate/confirm/[token]/page.tsxfrontend/src/features/admin/components/UserList.tsxfrontend/src/features/common/UserContext.tsxfrontend/src/apis/impersonation.tsfrontend/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 radiusrounded-2xl(16px),rounded-lg(12px),rounded-md(6px).
Frontend typography should use: H1text-xl font-semibold, H2text-lg font-semibold, Bodytext-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 useExportPdfButton,ExportPdfModal, andpdf-generator.tsutilities.
Files:
frontend/src/components/common/ImpersonationBanner.tsxfrontend/src/features/admin/components/ImpersonateDialog.tsxfrontend/src/app/impersonate/confirm/[token]/page.tsxfrontend/src/features/admin/components/UserList.tsxfrontend/src/features/common/UserContext.tsxfrontend/src/apis/impersonation.tsfrontend/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.tsxfrontend/src/features/admin/components/ImpersonateDialog.tsxfrontend/src/app/impersonate/confirm/[token]/page.tsxfrontend/src/features/admin/components/UserList.tsxfrontend/src/features/common/UserContext.tsxfrontend/src/apis/impersonation.tsfrontend/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 insrc/features/with subdirectories for: common, layout, login, settings, tasks, theme, onboarding, admin.
Frontend Task and Team sharing must useTaskShareModalandTeamShareModalcomponents.
Frontend Dify integration must useDifyAppSelectorandDifyParamsFormcomponents.
Files:
frontend/src/features/admin/components/ImpersonateDialog.tsxfrontend/src/features/admin/components/UserList.tsxfrontend/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__.pybackend/app/api/endpoints/impersonate_confirm.pybackend/app/models/impersonation.pybackend/app/core/security.pybackend/app/api/api.pybackend/alembic/versions/add_impersonation_tables.pybackend/app/api/endpoints/impersonation.pybackend/app/schemas/impersonation.pybackend/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__.pybackend/app/api/endpoints/impersonate_confirm.pybackend/app/models/impersonation.pybackend/app/core/security.pybackend/app/api/api.pybackend/app/api/endpoints/impersonation.pybackend/app/schemas/impersonation.pybackend/app/services/impersonation_service.py
backend/app/models/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
Create SQLAlchemy models in
app/models/for database tables, then runalembic revision --autogenerate -m 'description'to create migration.
Files:
backend/app/models/__init__.pybackend/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__.pybackend/app/api/endpoints/impersonate_confirm.pybackend/app/models/impersonation.pybackend/app/core/security.pybackend/app/api/api.pybackend/app/api/endpoints/impersonation.pybackend/app/schemas/impersonation.pybackend/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 insrc/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.pybackend/app/api/api.pybackend/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, thenalembic merge -m 'merge heads' <head1> <head2>, thenalembic upgrade head, and commit the merge migration.
Database migrations: Create withalembic revision --autogenerate -m 'description', apply withalembic upgrade head, check status withalembic current, rollback withalembic 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 inclient.tsand 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/docsendpoint) andbackend/app/schemas/when API schema changes.
Create Pydantic schema definitions inapp/schemas/for API models and CRD definitions.
Bot model binding should usebind_modelinagent_config(recommended) rather than legacymodelRef.
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.tsxfrontend/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_requestfunction is well-designed for middleware-style usage, gracefully returningNoneon 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
onClosehandler 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:exitImpersonationshould be stable or excluded from dependencies.If
exitImpersonationis not memoized withuseCallbackin the UserContext, this could cause the interval to reset on every context update. Additionally, callingexitImpersonation()directly in the interval callback withoutawaitmay cause race conditions.Please verify that
exitImpersonationis stable (memoized) inUserContext. 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
exitImpersonationfrom the dependency array if it's intentional to only use the initial reference.
65-72: Good error handling in exit handler.The
try/finallypattern ensuresisExitingis always reset regardless of whetherexitImpersonationsucceeds 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
UserContextparses 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
processingstate 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_atas the expiry time instead of the original request expiry, which correctly reflects the 24-hour session duration. The service method guaranteessession_expires_atis set upon approval.
94-115: Reject endpoint implementation is correct.Hardcoding
remaining_seconds=0for rejected requests is semantically appropriate since the request is no longer actionable.
24-27: Ignore Ruff B008 warnings for FastAPIDepends().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
exitImpersonationfunction, 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
ImpersonationRequestmodel 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 atString(64)is sufficient forsecrets.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
URLSearchParamsfor query parameter handling. Based on learnings, the module is correctly placed insrc/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). TheImpersonationInfointerface 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_responsehelper 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
LiteralforImpersonationStatusprovides 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
UserInfoWithImpersonationschema 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_requestmethod properly validates the target user, uses secure token generation withsecrets.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_agentto 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_fieldsproperly handles nested structures.
| @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}", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "*impersonation*" -type fRepository: wecode-ai/Wegent
Length of output: 359
🏁 Script executed:
cat -n backend/app/services/impersonation_service.pyRepository: 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 -20Repository: wecode-ai/Wegent
Length of output: 42
🏁 Script executed:
cat -n backend/app/api/endpoints/impersonation.pyRepository: 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.
| # Get target user | ||
| target_user = ( | ||
| db.query(User).filter(User.id == request.target_user_id).first() | ||
| ) | ||
|
|
||
| return request, target_user |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| } catch (error) { | ||
| toast({ | ||
| variant: 'destructive', | ||
| title: 'Failed to load request', | ||
| description: (error as Error).message, | ||
| }); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| if (token) { | ||
| fetchInfo(); | ||
| } | ||
| }, [token, toast]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| try { | ||
| // Decode JWT payload (base64) | ||
| const parts = token.split('.'); | ||
| if (parts.length !== 3) return; | ||
|
|
||
| const payload = JSON.parse(atob(parts[1])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| const parts = token.split('.'); | ||
| if (parts.length !== 3) return; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Summary
Changes
Backend
ImpersonationRequestandImpersonationAuditLogtablesImpersonationServicefor request lifecycle managementFrontend
ImpersonationBanner: Shows warning banner during active impersonationImpersonateDialog: Dialog for creating impersonation requests/impersonate/confirm/[token]for user authorizationSecurity Features
Test Plan
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.