Skip to content

Add impersonation feature for user management#11533

Merged
Meldiron merged 14 commits into1.8.xfrom
feat-user-impersonation
Mar 20, 2026
Merged

Add impersonation feature for user management#11533
Meldiron merged 14 commits into1.8.xfrom
feat-user-impersonation

Conversation

@eldadfux
Copy link
Copy Markdown
Member

Add impersonation feature for user management

  • Introduced a new API endpoint to update user impersonator capability.
  • Enhanced user model to include impersonator attributes.
  • Updated database schema to support impersonation.
  • Implemented impersonation logic in the request handling to allow users with impersonator capability to act as other users.
  • Added relevant API documentation for impersonation headers.

This feature allows users with the appropriate permissions to impersonate other users, enhancing flexibility in user management.

- Introduced a new API endpoint to update user impersonator capability.
- Enhanced user model to include impersonator attributes.
- Updated database schema to support impersonation.
- Implemented impersonation logic in the request handling to allow users with impersonator capability to act as other users.
- Added relevant API documentation for impersonation headers.

This feature allows users with the appropriate permissions to impersonate other users, enhancing flexibility in user management.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds user impersonation support: a new boolean impersonator attribute (with DB index) and API to toggle it; request handling that, when the current user has impersonator enabled and one of the headers X-Appwrite-Impersonate-User-Id, X-Appwrite-Impersonate-User-Email, or X-Appwrite-Impersonate-User-Phone is provided, selects a target user and replaces the request user context with the impersonated user while recording impersonation metadata. Updates include SDK key declarations, CORS headers, response models (Account/User), query validators, audit payloads, scope handling, a PATCH endpoint for impersonator, and extensive end-to-end tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding an impersonation feature for user management, which aligns with the comprehensive changeset across multiple files.
Description check ✅ Passed The description is directly related to the changeset, covering the key aspects: API endpoint, user model enhancements, database schema updates, impersonation logic, and API documentation.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-user-impersonation
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libexpat 2.7.4-r0 CVE-2026-32767 CRITICAL
zlib 1.3.1-r2 CVE-2026-22184 HIGH
zlib-dev 1.3.1-r2 CVE-2026-22184 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 12, 2026

Greptile Summary

This PR introduces a user impersonation feature that lets designated users (granted the impersonator flag via a new PATCH /v1/users/:userId/impersonator endpoint) act as any other project user by passing X-Appwrite-Impersonate-User-* request headers. The impersonation resolves in resources.php, the audit trail is correctly routed to the impersonator's identity in the Audits worker, and accessedAt updates are skipped for the target user during impersonated requests. The PR also migrates the custom Appwrite\Network\Validator\Email class to the upstream Utopia\Emails\Validator\Email.

Key issues found:

  • Silent impersonation failure (app/init/resources.php): When a supplied impersonation header resolves to no user (invalid ID, unrecognised email/phone), the code silently continues as the original impersonator without any error. Callers performing write operations may unknowingly mutate their own data believing they are acting as the target.
  • Phone number not normalised before lookup (app/init/resources.php): Email lookups are lowercased before querying, but phone lookups use the raw header value. Phone numbers supplied without the + prefix or with formatting differences will silently fail to resolve.
  • Self-impersonation not guarded (app/init/resources.php): No check prevents an impersonator from passing their own identity in the impersonation headers, producing confusing in-memory state where impersonatorUserId equals the user's own $id.
  • Test assertNull vs empty-string default (tests/e2e/Services/Users/UsersBase.php): Multiple assertions use assertNull($response['body']['impersonatorUserId']) but the response model defines 'default' => '', so the API returns "" rather than null for unset fields, causing these assertions to fail.

Confidence Score: 2/5

  • Not safe to merge — several functional bugs in the core impersonation resolution path and failing test assertions need to be resolved first.
  • The core feature logic in resources.php has three correctness issues: silent failure on unresolved impersonation targets (can cause inadvertent self-mutations), missing phone normalization (silent lookup failures), and no self-impersonation guard. Additionally, the test suite contains assertNull assertions that will fail against the model's empty-string default, meaning CI will not pass as-is. Prior threads also identified a privilege-escalation concern with the unconditional users.read scope grant and missing guards against impersonating privileged users, which remain unresolved in this revision.
  • app/init/resources.php (impersonation resolution logic) and tests/e2e/Services/Users/UsersBase.php (assertNull vs empty-string default) need the most attention before merging.

Important Files Changed

Filename Overview
app/init/resources.php Core impersonation resolution logic: looks up target user by ID/email/phone and swaps $user in-memory. Has three issues: silent failure when target not found, phone number not normalised before lookup, and self-impersonation not guarded.
app/controllers/shared/api.php Injects users.read scope for users with impersonator capability or active impersonatorUserId, and skips accessedAt update for impersonated requests. The users.read scope injection was flagged in previous review threads.
app/controllers/api/users.php New PATCH /v1/users/:userId/impersonator endpoint correctly gated behind users.write (admin/key only) and performs a clean partial document update.
src/Appwrite/Platform/Workers/Audits.php Audit records now attribute actions to the impersonator (correct) and embed the impersonated user's details in nested payload data. Both the array and non-array branches of the payload extension correctly include impersonatedUserId.
tests/e2e/Services/Users/UsersBase.php Comprehensive e2e test coverage for the new feature, but multiple assertNull($response['body']['impersonatorUserId']) assertions will likely fail because the response model returns the empty-string default "" rather than null for unset fields.
src/Appwrite/Utopia/Response/Model/User.php Added impersonator (boolean) and impersonatorUserId (string) rules with corrected descriptions compared to the original misleading wording flagged in previous review.
src/Appwrite/Utopia/Response/Model/Account.php Added same impersonator and impersonatorUserId fields as User model; descriptions are accurate and consistent.
app/config/collections/common.php Added impersonator boolean field and a DATABASE_INDEX_KEY index on it. The low-cardinality boolean index was flagged in a previous review thread as providing little query-planning benefit.
src/Appwrite/Network/Validator/Email.php Deleted in favour of the upstream Utopia\Emails\Validator\Email; all references updated consistently across the codebase.

Last reviewed commit: "fixes"

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

🔄 PHP-Retry Summary

Flaky tests detected across commits:

Commit 468eff6 - 4 flaky tests
Test Retries Total Time Details
UsageTest::testDatabaseStatsCollectionsAPI 1 10.26s Logs
LegacyCustomServerTest::testOneToOneRelationship 1 241.88s Logs
LegacyTransactionsConsoleClientTest::testTransactionSizeLimit 1 240.20s Logs
LegacyTransactionsConsoleClientTest::testBulkOperations 1 240.47s Logs
Commit 85fcc52 - 4 flaky tests
Test Retries Total Time Details
FunctionsScheduleTest::testCreateScheduledExecution 1 192.21s Logs
FunctionsScheduleTest::testCreateScheduledAtExecution 1 129.71s Logs
UsageTest::testFunctionsStats 1 10.14s Logs
UsageTest::testPrepareSitesStats 1 17ms Logs
Commit b81f3f8 - 4 flaky tests
Test Retries Total Time Details
UsageTest::testFunctionsStats 1 10.14s Logs
UsageTest::testPrepareSitesStats 1 7ms Logs
TablesDBCustomClientTest::testOrQueries 1 240.46s Logs
TablesDBCustomServerTest::testEnforceCollectionPermissions 1 84ms Logs
Commit aa89128 - 2 flaky tests
Test Retries Total Time Details
UsageTest::testFunctionsStats 1 10.17s Logs
UsageTest::testPrepareSitesStats 1 7ms Logs
Commit efeb3b4 - 2 flaky tests
Test Retries Total Time Details
UsageTest::testFunctionsStats 1 10.14s Logs
UsageTest::testPrepareSitesStats 1 8ms Logs

Note: Flaky test results are tracked for the last 5 commits

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

✨ Benchmark results

  • Requests per second: 1,984
  • Requests with 200 status code: 357,218
  • P99 latency: 0.09148974

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 1,984 1,231
200 357,218 221,595
P99 0.09148974 0.181669609

Copy link
Copy Markdown
Contributor

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/shared/api.php`:
- Around line 341-345: The current logic appends a global 'users.read' scope
when $user->getAttribute('impersonatorUserId') is present, which escalates
impersonated sessions to project-wide user access; instead, stop adding a global
scope and grant a scoped permission limited to the impersonation target (e.g.,
create and add a per-user scope like "users.read:{targetUserId}" using
$user->getAttribute('id') or otherwise record the impersonation target in the
session/scopes), or remove adding any users.read scope and ensure downstream
authorization checks in users-related handlers validate that the active session
is only allowed to access the impersonated user's records via the impersonation
attribute.

In `@app/init/resources.php`:
- Around line 482-490: The code replaces the authenticated caller in $user with
the impersonated $targetUser; instead, preserve the original actor and introduce
an explicit effective user for impersonation. Do not reassign $user—store the
original caller id in $impersonatorUserId = $user->getId(); create an
$effectiveUser (or $actingUser) that is a clone of $targetUser and
setAttribute('impersonatorUserId', $impersonatorUserId) on that clone, then use
$effectiveUser when performing actions as the target while keeping $user for
session re-verification; also set DB metadata to record both actor and effective
IDs (e.g. keep $dbForProject->setMetadata('user', $user->getId()) and add
$dbForProject->setMetadata('effective_user', $effectiveUser->getId()), same for
$dbForPlatform) so auditing preserves the real actor.
- Around line 472-480: The code chooses $dbForPlatform when APP_MODE_ADMIN ===
$mode which causes admin (Console) requests against non-console projects to look
up impersonation targets in the wrong DB; change the $userDb assignment (the
ternary that sets $userDb) so it selects $dbForPlatform only when
$project->getId() === 'console' and otherwise uses $dbForProject (i.e., $userDb
= ($project->getId() === 'console') ? $dbForPlatform : $dbForProject) so the
impersonation lookups in the block that calls
$userDb->getAuthorization()->skip(...), $userDb->getDocument('users',
$impersonateUserId) and $userDb->findOne('users', ...) query the project users
collection for non-console projects even in admin mode.

In `@src/Appwrite/Platform/Tasks/Specs.php`:
- Around line 163-180: Update the descriptions for the ImpersonateUserId,
ImpersonateUserEmail, and ImpersonateUserPhone header specs (the array entries
keyed as 'ImpersonateUserId', 'ImpersonateUserEmail', 'ImpersonateUserPhone') to
explicitly state these headers only work on requests already authenticated as a
user who has the impersonator capability and cannot be used with X-Appwrite-Key
alone; apply the same wording change to the other identical header spec blocks
elsewhere in the file (the repeated Impersonate* entries) so all occurrences
clearly indicate they require prior user authentication with impersonator
capability.

In `@src/Appwrite/Utopia/Response/Model/Account.php`:
- Around line 119-124: The description for the 'impersonatorUserId' field in the
Account model is ambiguous; update the rule for 'impersonatorUserId' to
explicitly state whether it refers to the original actor (the user who performed
impersonation) or the impersonation target — e.g., if it's the original actor,
change the description to: "ID of the original actor who initiated impersonation
(the impersonator); present only when the session was created using
impersonation headers" — otherwise, rename the field to 'impersonatedUserId' and
update its description accordingly so it isn't redundant with $id; adjust the
'impersonatorUserId' rule in Account.php to reflect this clarified contract.

In `@src/Appwrite/Utopia/Response/Model/User.php`:
- Around line 149-154: The description for the schema rule added via
addRule('impersonatorUserId', ...) is misleading: change it to clearly state
this field holds the ID of the user performing the impersonation (the original
actor), not the impersonation target; update the 'description' value for
impersonatorUserId in the User model to something like "ID of the user
performing the impersonation (original actor); present only when the request
used impersonation headers" so it matches the runtime behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 09aa7318-817c-488c-949d-38140020e8cb

📥 Commits

Reviewing files that changed from the base of the PR and between d8f7337 and 8304a8e.

📒 Files selected for processing (8)
  • app/config/collections/common.php
  • app/controllers/api/users.php
  • app/controllers/shared/api.php
  • app/init/resources.php
  • src/Appwrite/Platform/Tasks/Specs.php
  • src/Appwrite/Utopia/Database/Validator/Queries/Users.php
  • src/Appwrite/Utopia/Response/Model/Account.php
  • src/Appwrite/Utopia/Response/Model/User.php

Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/Services/Users/UsersBase.php`:
- Around line 2635-2669: The test testListUsersFilterByImpersonator currently
only checks response shape and a single contains, so update it to actually
assert the impersonator filter: create a control user (e.g.,
$userWithoutImpersonator) with impersonator=false, call the PATCH endpoint only
for $userWithImpersonator (as already done), then for the GET with
Query::equal('impersonator',[true]) assert that every returned user in
$response['body']['users'] has 'impersonator'===true and that
$userWithImpersonator['body']['$id'] is present; for the GET with
Query::equal('impersonator',[false]) assert every returned user has
'impersonator'===false and that $userWithoutImpersonator['body']['$id'] is
present (or assert $userWithImpersonator['body']['$id'] is absent) to prove
inclusion/exclusion.
- Around line 2589-2630: The test testImpersonationUsersReadScope currently only
checks the happy path with the x-appwrite-impersonate-user-id header; add an
extra GET /users request using the same session (use $session['body']['secret']
as before) but without the 'x-appwrite-impersonate-user-id' header and assert it
returns 401 to ensure users.read is only available during active impersonation;
update the test around the existing $listHeaders/$users block in
UsersBase::testImpersonationUsersReadScope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a01810ee-8f97-4b1f-b32f-63d074321c1f

📥 Commits

Reviewing files that changed from the base of the PR and between 8304a8e and 29d9c13.

📒 Files selected for processing (1)
  • tests/e2e/Services/Users/UsersBase.php

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (6)
demo/src/api.ts (2)

83-93: Consider using else if or a switch statement for mutually exclusive impersonation types.

Since ImpersonationHeader is a discriminated union where only one type can be active, the current structure evaluates all three conditions even after a match. While functionally correct, an else if chain or switch statement would be more semantically accurate and slightly more efficient.

♻️ Optional refactor using switch
-  if (impersonation.type === 'id') {
-    headers['x-appwrite-impersonate-user-id'] = impersonation.value;
-  }
-
-  if (impersonation.type === 'email') {
-    headers['x-appwrite-impersonate-user-email'] = impersonation.value;
-  }
-
-  if (impersonation.type === 'phone') {
-    headers['x-appwrite-impersonate-user-phone'] = impersonation.value;
-  }
+  switch (impersonation.type) {
+    case 'id':
+      headers['x-appwrite-impersonate-user-id'] = impersonation.value;
+      break;
+    case 'email':
+      headers['x-appwrite-impersonate-user-email'] = impersonation.value;
+      break;
+    case 'phone':
+      headers['x-appwrite-impersonate-user-phone'] = impersonation.value;
+      break;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/api.ts` around lines 83 - 93, The three separate if checks on
impersonation.type should be made mutually exclusive to reflect the
ImpersonationHeader discriminated union; update the logic that sets headers (the
block referencing impersonation.type and keys 'x-appwrite-impersonate-user-id',
'x-appwrite-impersonate-user-email', 'x-appwrite-impersonate-user-phone') to use
an else-if chain or a switch on impersonation.type so only one header is set per
call, leaving the rest unchanged.

102-104: Type assertion on 204 response may cause runtime issues.

Returning {} as T for 204 responses can be unsafe if T expects specific properties. Currently deleteCurrentSession uses Record<string, never> which is compatible, but if other callers expect different types, this could silently return an unexpected shape.

♻️ Consider returning undefined or void for 204 responses

One approach is to change the return type signature to indicate that 204 responses return undefined:

-export const request = async <T>({
+export const request = async <T, AllowEmpty extends boolean = false>({
   endpoint,
   projectId,
   path,
   method = 'GET',
   impersonation = { type: 'none' },
   payload,
-}: RequestOptions): Promise<T> => {
+}: RequestOptions & { allowEmpty?: AllowEmpty }): Promise<AllowEmpty extends true ? T | undefined : T> => {

Alternatively, for this demo app, the current approach is acceptable since only deleteCurrentSession hits this path and uses Record<string, never>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/api.ts` around lines 102 - 104, The current 204 branch returns "{}
as T" which can be unsafe; change the branch to return undefined (i.e. "return
undefined") and update the method's generic return type to reflect that 204
yields no body (e.g. Promise<T | undefined> or Promise<void> as appropriate).
Update callers such as deleteCurrentSession to accept the new undefined result
(or keep deleteCurrentSession typed as Record<string, never> if you prefer to
leave that specific caller unchanged). Ensure the central response handling
function (the block with "if (response.status === 204)") and any exported API
function signatures are updated consistently to avoid unsafe asserts.
demo/src/App.tsx (2)

21-21: localStorage access can throw in restricted contexts.

In some browsers (Safari private browsing, certain iframe sandboxes), localStorage access throws. While acceptable for a demo app, consider wrapping in try-catch for robustness.

♻️ Defensive localStorage access
-const getStoredValue = (key: string, fallback: string) => localStorage.getItem(key) ?? fallback;
+const getStoredValue = (key: string, fallback: string) => {
+  try {
+    return localStorage.getItem(key) ?? fallback;
+  } catch {
+    return fallback;
+  }
+};

Similarly for the useEffect setters on lines 33-39:

 useEffect(() => {
-  localStorage.setItem(storageKeys.endpoint, endpoint);
+  try {
+    localStorage.setItem(storageKeys.endpoint, endpoint);
+  } catch {
+    // Storage unavailable (e.g., private browsing)
+  }
 }, [endpoint]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/App.tsx` at line 21, Wrap all direct localStorage accesses in
try/catch to avoid thrown exceptions in restricted contexts: update
getStoredValue to catch errors and return the fallback on any exception, and
similarly wrap any localStorage.setItem/removeItem calls used inside the
component's useEffect setters so they silently fail (and optionally log) instead
of throwing; locate functions/places by the getStoredValue function and the
useEffect blocks that call localStorage.setItem/removeItem and add try/catch
around those calls, returning fallback or skipping the write on error.

96-110: Consider handling the case when type is 'none'.

The setTarget function receives ImpersonationHeader['type'] which includes 'none', but there's no explicit handling for it. While currently no caller passes 'none', adding explicit handling would make the function more defensive.

♻️ Add explicit 'none' handling
 const setTarget = (type: ImpersonationHeader['type'], user: User) => {
+  if (type === 'none') {
+    setImpersonation({ type: 'none' });
+    return;
+  }
+
   if (type === 'id') {
     setImpersonation({ type, value: user.$id });
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/App.tsx` around lines 96 - 110, setTarget currently ignores the
'none' option from ImpersonationHeader['type']; add an explicit branch for when
type === 'none' to clear the impersonation state via setImpersonation. Update
the setTarget function to detect type === 'none' and call setImpersonation with
the appropriate "cleared" value your state expects (e.g. null/undefined or an
explicit { type: 'none', value: '' } shape) so the UI/state is defensively reset
when 'none' is passed.
demo/src/index.css (2)

69-76: Consider removing quotes around SFMono-Regular.

Stylelint prefers unquoted font family names when they don't contain spaces or special characters that require quoting. The hyphen doesn't require quotes.

♻️ Remove unnecessary quotes
-  font-family: 'SFMono-Regular', ui-monospace, monospace;
+  font-family: SFMono-Regular, ui-monospace, monospace;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/index.css` around lines 69 - 76, In the code block for the CSS
selector "code" in demo/src/index.css, remove the unnecessary quotes around the
font-family token 'SFMono-Regular' so the font-family declaration reads with an
unquoted SFMono-Regular followed by the fallback list (ui-monospace, monospace);
update the font-family line in the "code" rule accordingly to satisfy
stylelint's preference for unquoted font names without spaces or special
characters.

9-9: CSS keyword casing: optimizeLegibility should be lowercase.

Per CSS specification, keyword values should be lowercase. Stylelint flags this as an error.

♻️ Fix keyword case
-  text-rendering: optimizeLegibility;
+  text-rendering: optimizelegibility;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demo/src/index.css` at line 9, The text-rendering declaration in index.css
uses the camelCase keyword "optimizeLegibility"; update the keyword to lowercase
so the declaration reads text-rendering: optimizelegibility; to satisfy
stylelint's keyword-case rule—modify the value for the text-rendering property
in demo/src/index.css accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@demo/src/api.ts`:
- Around line 83-93: The three separate if checks on impersonation.type should
be made mutually exclusive to reflect the ImpersonationHeader discriminated
union; update the logic that sets headers (the block referencing
impersonation.type and keys 'x-appwrite-impersonate-user-id',
'x-appwrite-impersonate-user-email', 'x-appwrite-impersonate-user-phone') to use
an else-if chain or a switch on impersonation.type so only one header is set per
call, leaving the rest unchanged.
- Around line 102-104: The current 204 branch returns "{} as T" which can be
unsafe; change the branch to return undefined (i.e. "return undefined") and
update the method's generic return type to reflect that 204 yields no body (e.g.
Promise<T | undefined> or Promise<void> as appropriate). Update callers such as
deleteCurrentSession to accept the new undefined result (or keep
deleteCurrentSession typed as Record<string, never> if you prefer to leave that
specific caller unchanged). Ensure the central response handling function (the
block with "if (response.status === 204)") and any exported API function
signatures are updated consistently to avoid unsafe asserts.

In `@demo/src/App.tsx`:
- Line 21: Wrap all direct localStorage accesses in try/catch to avoid thrown
exceptions in restricted contexts: update getStoredValue to catch errors and
return the fallback on any exception, and similarly wrap any
localStorage.setItem/removeItem calls used inside the component's useEffect
setters so they silently fail (and optionally log) instead of throwing; locate
functions/places by the getStoredValue function and the useEffect blocks that
call localStorage.setItem/removeItem and add try/catch around those calls,
returning fallback or skipping the write on error.
- Around line 96-110: setTarget currently ignores the 'none' option from
ImpersonationHeader['type']; add an explicit branch for when type === 'none' to
clear the impersonation state via setImpersonation. Update the setTarget
function to detect type === 'none' and call setImpersonation with the
appropriate "cleared" value your state expects (e.g. null/undefined or an
explicit { type: 'none', value: '' } shape) so the UI/state is defensively reset
when 'none' is passed.

In `@demo/src/index.css`:
- Around line 69-76: In the code block for the CSS selector "code" in
demo/src/index.css, remove the unnecessary quotes around the font-family token
'SFMono-Regular' so the font-family declaration reads with an unquoted
SFMono-Regular followed by the fallback list (ui-monospace, monospace); update
the font-family line in the "code" rule accordingly to satisfy stylelint's
preference for unquoted font names without spaces or special characters.
- Line 9: The text-rendering declaration in index.css uses the camelCase keyword
"optimizeLegibility"; update the keyword to lowercase so the declaration reads
text-rendering: optimizelegibility; to satisfy stylelint's keyword-case
rule—modify the value for the text-rendering property in demo/src/index.css
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04a108c1-7f7e-48ab-832a-782a02f38dbb

📥 Commits

Reviewing files that changed from the base of the PR and between 29d9c13 and e409524.

⛔ Files ignored due to path filters (6)
  • demo/package-lock.json is excluded by !**/package-lock.json
  • demo/public/favicon.svg is excluded by !**/*.svg
  • demo/public/icons.svg is excluded by !**/*.svg
  • demo/src/assets/hero.png is excluded by !**/*.png
  • demo/src/assets/react.svg is excluded by !**/*.svg
  • demo/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (19)
  • app/config/cors.php
  • app/controllers/shared/api.php
  • demo/.env
  • demo/.env.example
  • demo/.gitignore
  • demo/README.md
  • demo/eslint.config.js
  • demo/index.html
  • demo/package.json
  • demo/src/App.css
  • demo/src/App.tsx
  • demo/src/api.ts
  • demo/src/index.css
  • demo/src/main.tsx
  • demo/tsconfig.app.json
  • demo/tsconfig.json
  • demo/tsconfig.node.json
  • demo/vite.config.ts
  • tests/e2e/Services/Users/UsersBase.php
✅ Files skipped from review due to trivial changes (3)
  • demo/.env
  • demo/.gitignore
  • demo/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/shared/api.php

Copy link
Copy Markdown
Member

@lohanidamodar lohanidamodar left a comment

Choose a reason for hiding this comment

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

Looks good

Copy link
Copy Markdown
Member

@lohanidamodar lohanidamodar left a comment

Choose a reason for hiding this comment

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

Looks good

@blacksmith-sh

This comment has been minimized.

@Meldiron
Copy link
Copy Markdown
Contributor

@greptile

@Meldiron Meldiron merged commit 875637b into 1.8.x Mar 20, 2026
85 of 86 checks passed
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.

3 participants