Skip to content

Added auth option to allow invalidating existing sessions#10010

Merged
abnegate merged 10 commits intoappwrite:1.8.xfrom
ArnabChatterjee20k:dat-522
Jun 18, 2025
Merged

Added auth option to allow invalidating existing sessions#10010
abnegate merged 10 commits intoappwrite:1.8.xfrom
ArnabChatterjee20k:dat-522

Conversation

@ArnabChatterjee20k
Copy link
Copy Markdown
Member

@ArnabChatterjee20k ArnabChatterjee20k commented Jun 14, 2025

What does this PR do?

(Provide a description of what this PR does and why it's needed.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Screenshots may also be helpful.)

Related PRs and Issues

  • (Related PR or issue)

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs?

Summary by CodeRabbit

  • New Features

    • Added a project setting to control session invalidation upon user password changes.
    • Introduced an API endpoint to update this session invalidation setting for existing projects.
  • Bug Fixes

    • Ensured all active user sessions except the current one are invalidated after password changes when the setting is enabled.
  • Tests

    • Added and enhanced tests to verify session invalidation functionality linked to the new project setting.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jun 14, 2025

Walkthrough

A new project-level boolean attribute invalidateSessions was added to control session invalidation on password changes. A PATCH API endpoint was introduced to update this attribute per project. Password update logic now invalidates all other user sessions except the current one if the flag is enabled. Model, configuration, and end-to-end tests were updated accordingly.

Changes

File(s) Change Summary
app/controllers/api/projects.php Added invalidateSessions boolean attribute (default true) to project creation; added PATCH endpoint to update it.
app/controllers/api/users.php, app/controllers/api/account.php Modified password update to invalidate all user sessions except current if project’s invalidateSessions flag is true.
src/Appwrite/Utopia/Response/Model/Project.php Added authInvalidateSessions boolean attribute and filter logic to Project model.
app/config/console.php Added invalidateSessions key set to true in console project auths config; fixed trailing comma.
tests/e2e/Scopes/ProjectCustom.php Added method to update project’s invalidateSessions property via API in tests.
tests/e2e/Services/Projects/ProjectsConsoleClientTest.php Added test verifying updating invalidateSessions flag via PATCH API endpoint.
tests/e2e/Services/Users/UsersBase.php Enhanced password update test to enable invalidateSessions, verify sessions are cleared, then disable it again.
tests/e2e/Services/Account/AccountCustomClientTest.php Enhanced password update test to verify multiple logins before update and only current session remains after update.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant DB

    Client->>API: PATCH /v1/projects/:projectId/auth/session-invalidation (invalidateSessions)
    API->>DB: Find project by projectId
    API->>DB: Update project's auths['invalidateSessions']
    API-->>Client: Return updated project document
Loading
sequenceDiagram
    participant User
    participant API
    participant DB
    participant Cache

    User->>API: PATCH /v1/users/{userId}/password (new password)
    API->>DB: Update user password and history
    API->>DB: Check project.auths['invalidateSessions']
    alt invalidateSessions is true
        API->>DB: Identify current session
        API->>DB: Delete all other user sessions except current
        API->>Cache: Purge cached user document
    end
    API-->>User: Return updated user document
Loading

Poem

🐇 A flag to clear sessions, fresh and bright,
When passwords change, old keys take flight.
PATCH sets the rule, tests hop in line,
Ensuring user safety, one session at a time.
With code so swift and logic so keen,
Our project’s secure, a hopping machine! 🐇🔐

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jun 14, 2025

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
binutils 2.42-r0 CVE-2025-0840 HIGH
libexpat 2.6.4-r0 CVE-2024-8176 HIGH
libxml2 2.12.7-r0 CVE-2024-56171 HIGH
libxml2 2.12.7-r0 CVE-2025-24928 HIGH
libxml2 2.12.7-r0 CVE-2025-27113 HIGH
libxml2 2.12.7-r0 CVE-2025-32414 HIGH
libxml2 2.12.7-r0 CVE-2025-32415 HIGH
pyc 3.12.9-r0 CVE-2024-12718 HIGH
pyc 3.12.9-r0 CVE-2025-4138 HIGH
pyc 3.12.9-r0 CVE-2025-4517 HIGH
python3 3.12.9-r0 CVE-2024-12718 HIGH
python3 3.12.9-r0 CVE-2025-4138 HIGH
python3 3.12.9-r0 CVE-2025-4517 HIGH
python3-pyc 3.12.9-r0 CVE-2024-12718 HIGH
python3-pyc 3.12.9-r0 CVE-2025-4138 HIGH
python3-pyc 3.12.9-r0 CVE-2025-4517 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2024-12718 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2025-4138 HIGH
python3-pycache-pyc0 3.12.9-r0 CVE-2025-4517 HIGH
sqlite-libs 3.45.3-r1 CVE-2025-29087 HIGH
xz 5.6.2-r0 CVE-2025-31115 HIGH
xz-libs 5.6.2-r0 CVE-2025-31115 HIGH
golang.org/x/crypto v0.31.0 CVE-2025-22869 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

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

🔭 Outside diff range comments (1)
app/controllers/api/projects.php (1)

118-132: ⚠️ Potential issue

Store a strict boolean to avoid truthy/​falsy surprises

$onPasswordChange can arrive as the string 'true'/'false', '1'/'0', or a native bool depending on HTTP client & framework parsing.
Down-stream logic (users controller) expects a real boolean. Cast explicitly, mirroring the pattern used for $status later in the file:

+$onPasswordChange = ($onPasswordChange === '1' || $onPasswordChange === 'true' ||
+    $onPasswordChange === 1   || $onPasswordChange === true);

Place this right before $auths = [ to guarantee we persist a strict bool and keep the stored schema consistent.

🧹 Nitpick comments (4)
tests/e2e/Scopes/ProjectCustom.php (1)

203-215: Unnecessary array_merge() call adds overhead & suppresses static analysis

array_merge() with a single argument is a no-op and flags on recent PHP-Stan/Psalm rules.
Just pass the headers array directly:

-        $response = $this->client->call(Client::METHOD_PATCH, '/projects/' . self::$project['$id'] . '/auth/password-change', array_merge([
+        $response = $this->client->call(
+            Client::METHOD_PATCH,
+            '/projects/' . self::$project['$id'] . '/auth/password-change',
+            [
                 'origin' => 'http://localhost',
                 'content-type' => 'application/json',
                 'cookie' => 'a_session_console=' . $this->getRoot()['session'],
                 'x-appwrite-project' => 'console',
-            ]), [
+            ],
+            [
                 'onPasswordChange' => $value,
             ]
-        ]);
+        );
app/controllers/api/users.php (1)

1357-1361: Stale sessions array returned to caller

After deleting the session documents, $user still contains the old sessions list, so the response shows sessions that no longer exist.
Consider refreshing the user document or purging/clearing the sessions attribute before returning.

tests/e2e/Services/Users/UsersBase.php (1)

1132-1139: Add explicit assertions on the sessions payload

The test rightfully checks that no sessions remain, but it does so by assuming the response body contains a sessions key. To make intent clear and avoid silent shape changes in the API:

$this->assertEquals(200, $sessions['headers']['status-code']);
+$this->assertArrayHasKey('sessions', $sessions['body']);
$this->assertIsArray($sessions['body']);
$this->assertEmpty($sessions['body']['sessions']);

Including the assertArrayHasKey protects against accidental contract changes where the field is renamed or removed.

app/controllers/api/projects.php (1)

97-97: Clarify parameter semantics & wording

The label 'Auth option to allow invalidating existing sessions' is vague and doesn’t communicate when invalidation happens.
Consider a clearer name/description, e.g. “Invalidate all user sessions when a user’s password is changed.”

Tagging will also improve autogenerated API docs and SDK code-gen.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc7b310 and 74614e8.

⛔ Files ignored due to path filters (1)
  • docs/references/databases/update-auth-on-password-change.md is excluded by !docs/references/**
📒 Files selected for processing (6)
  • app/controllers/api/projects.php (3 hunks)
  • app/controllers/api/users.php (1 hunks)
  • src/Appwrite/Utopia/Response/Model/Project.php (2 hunks)
  • tests/e2e/Scopes/ProjectCustom.php (1 hunks)
  • tests/e2e/Services/Projects/ProjectsConsoleClientTest.php (1 hunks)
  • tests/e2e/Services/Users/UsersBase.php (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: scan

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

🧹 Nitpick comments (1)
tests/e2e/Services/Account/AccountCustomClientTest.php (1)

483-495: Trim 5 s from test by ditching the per-iteration sleep(1)

The one-second pause inside the loop slows the suite without adding functional value.
If you only need distinct timestamps, usleep(100_000) (0.1 s) is usually enough; otherwise the delay can be removed entirely.

-    $this->assertEquals(201, $response['headers']['status-code']);
-    sleep(1);
+    $this->assertEquals(201, $response['headers']['status-code']);
+    // Brief pause to avoid potential race conditions while keeping the test fast
+    usleep(100_000); // 0.1 s
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d62ada and 56494ca.

📒 Files selected for processing (2)
  • app/controllers/api/account.php (1 hunks)
  • tests/e2e/Services/Account/AccountCustomClientTest.php (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: scan-pr / osv-scan
  • GitHub Check: Check if utopia-php/database changed
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Setup & Build Appwrite Image
  • GitHub Check: Linter
  • GitHub Check: scan
🔇 Additional comments (1)
app/controllers/api/account.php (1)

2846-2853: Should this honor the project-level authInvalidateSessions flag?

The PR description mentions a new per-project boolean to toggle session invalidation on password change.
The users controller (updatePassword in users.php) checks that flag, but this account endpoint invalidates unconditionally. If the intent is that self-service password changes always revoke other sessions, ignore this note. Otherwise, wrap the block in:

if ($project->getAttribute('authInvalidateSessions', true)) { … }

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: 3

♻️ Duplicate comments (2)
tests/e2e/Services/Account/AccountCustomClientTest.php (2)

483-495: Address the session creation logic issue

Based on the past review comment, creating multiple sessions when you already have an active one should fail according to the application's business rules. This test is creating 5 sessions successfully, which may not reflect the actual expected behavior.


526-561: Implement negative assertions for stronger session validation

The current test only verifies that the session count is 1 and checks for 404 responses when accessing invalidated sessions. However, it doesn't prove that the stale session cookies are actually unusable for API requests.

Consider capturing the session cookies before password change and then making authenticated requests with each stale cookie to assert they return 401 unauthorized status. This would provide stronger evidence that sessions are truly invalidated.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56494ca and d053282.

📒 Files selected for processing (2)
  • app/controllers/api/account.php (1 hunks)
  • tests/e2e/Services/Account/AccountCustomClientTest.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/api/account.php
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: E2E Service Test (Messaging)
  • GitHub Check: E2E Service Test (Sites)
  • GitHub Check: E2E Service Test (Users)
  • GitHub Check: E2E Service Test (Locale)
  • GitHub Check: E2E Service Test (Webhooks)
  • GitHub Check: E2E Service Test (FunctionsSchedule)
  • GitHub Check: E2E Service Test (Functions)
  • GitHub Check: E2E Service Test (Databases)
  • GitHub Check: Benchmark
🔇 Additional comments (2)
tests/e2e/Services/Account/AccountCustomClientTest.php (2)

564-578: Good implementation of disabled invalidation testing

This section properly addresses the past review comment about testing when invalidateSessions is false. The logic correctly creates multiple sessions and prepares to verify they remain valid after password update.


601-610: Good verification of session persistence

This section correctly verifies that when session invalidation is disabled, all sessions remain accessible after password update. The logic properly iterates through all session IDs and confirms they return 200 status codes.

Comment on lines +635 to +646
foreach ($allSessions as $sessionId) {
if ($currentSessionId !== $sessionId) {
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));

$this->assertEquals(404, $response['headers']['status-code']);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Verify the session invalidation logic

The test fetches all sessions after password update and then checks that non-current sessions return 404. However, there's a logical issue: if session invalidation is working correctly, the getAllSessions call should only return the current session, making the foreach loop unnecessary.

Consider asserting that only one session exists before iterating, or restructure to use the previously captured session IDs for validation:

+        $this->assertEquals(1, $response['body']['total']);
         $allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);
📝 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
foreach ($allSessions as $sessionId) {
if ($currentSessionId !== $sessionId) {
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));
$this->assertEquals(404, $response['headers']['status-code']);
}
}
// After fetching all sessions, assert only the current session remains
$this->assertEquals(1, $response['body']['total']);
$allSessions = array_map(fn ($sessionDetails) => $sessionDetails['$id'], $response['body']['sessions']);
foreach ($allSessions as $sessionId) {
if ($currentSessionId !== $sessionId) {
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/'.$sessionId, \array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]));
$this->assertEquals(404, $response['headers']['status-code']);
}
}
🤖 Prompt for AI Agents
In tests/e2e/Services/Account/AccountCustomClientTest.php around lines 635 to
646, the test incorrectly loops over all sessions after password update, but if
session invalidation works, only the current session should remain. Fix this by
first asserting that the list of sessions contains only one session (the current
one) before any iteration, or adjust the test to validate session IDs captured
before the password update to confirm invalidation, removing unnecessary looping
over all sessions.

Comment on lines +614 to +622
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'password' => $newPassword,
'oldPassword' => $newPassword,
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Another password inconsistency issue

Similar to the earlier issue, using the same value for both password and oldPassword parameters doesn't make logical sense for a password update operation.

-        ]), [
-            'password' => $newPassword,
-            'oldPassword' => $newPassword,
-        ]);
+        ]), [
+            'password' => 'final-new-password',
+            'oldPassword' => $newPassword,
+        ]);
📝 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
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'password' => $newPassword,
'oldPassword' => $newPassword,
]);
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'password' => 'final-new-password',
'oldPassword' => $newPassword,
]);
🤖 Prompt for AI Agents
In tests/e2e/Services/Account/AccountCustomClientTest.php around lines 614 to
622, the password update call incorrectly uses the same value for both
'password' and 'oldPassword' parameters. To fix this, ensure 'oldPassword' holds
the current password value and 'password' holds the new password value to
correctly represent a password change operation.

Comment on lines +589 to +597
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
]), [
'password' => $newPassword,
'oldPassword' => $newPassword,
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent password usage in test

The test is updating the password using the same value for both password and oldPassword parameters ($newPassword). This seems incorrect - typically you would use the current password as oldPassword and a different value for the new password.

-        ]), [
-            'password' => $newPassword,
-            'oldPassword' => $newPassword,
-        ]);
+        ]), [
+            'password' => 'another-new-password',
+            'oldPassword' => $newPassword,
+        ]);

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

🤖 Prompt for AI Agents
In tests/e2e/Services/Account/AccountCustomClientTest.php around lines 589 to
597, the test incorrectly uses the same value for both 'password' and
'oldPassword' parameters when calling the password update endpoint. To fix this,
set 'oldPassword' to the current password value and 'password' to the new
password value, ensuring the test correctly simulates changing from the old
password to a new one.

@abnegate abnegate merged commit 53a00de into appwrite:1.8.x Jun 18, 2025
64 of 65 checks passed
This was referenced Jul 21, 2025
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