diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 8ff7c12cae..ac8938273f 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -117,14 +117,19 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor $hashedPassword = null; $isHashed = !$hash instanceof Plaintext; + + $defaultHash = new ProofsPassword(); if (!empty($password)) { if (!$isHashed) { // Password was never hashed, hash it with the default hash - $defaultHash = new ProofsPassword(); $hashedPassword = $defaultHash->hash($password); $hash = $defaultHash->getHash(); } else { $hashedPassword = $password; } + } else { + // when password is not provided, plaintext was set as the default hash causing the issue + $hash = $defaultHash->getHash(); + $isHashed = !$hash instanceof Plaintext; } $user = new Document([ @@ -160,7 +165,7 @@ function createUser(Hash $hash, string $userId, ?string $email, ?string $passwor 'emailIsFree' => $emailCanonical?->isFree(), ]); - if (!$isHashed) { + if (!$isHashed && !empty($password)) { $hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]); } @@ -2627,7 +2632,8 @@ App::post('/v1/users/:userId/jwts') $session = \count($sessions) > 0 ? $sessions[\count($sessions) - 1] : new Document(); } else { // Find by ID - foreach ($sessions as $loopSession) { /** @var Utopia\Database\Document $loopSession */ + foreach ($sessions as $loopSession) { + /** @var Utopia\Database\Document $loopSession */ if ($loopSession->getId() == $sessionId) { $session = $loopSession; break; diff --git a/tests/e2e/Services/Users/UsersConsoleClientTest.php b/tests/e2e/Services/Users/UsersConsoleClientTest.php index 967104f5db..24e0f6868b 100644 --- a/tests/e2e/Services/Users/UsersConsoleClientTest.php +++ b/tests/e2e/Services/Users/UsersConsoleClientTest.php @@ -6,6 +6,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideConsole; +use Utopia\Database\Helpers\ID; class UsersConsoleClientTest extends Scope { @@ -45,4 +46,39 @@ class UsersConsoleClientTest extends Scope $this->assertIsArray($response['body']['users']); $this->assertIsArray($response['body']['sessions']); } + + public function testCreateUserWithoutPasswordThenSetPassword() + { + // Create a user with email but without password + $userId = ID::unique(); + $email = $userId . '@example.com'; + + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + 'userId' => $userId, + 'email' => $email, + // no password provided + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['$id']); + $this->assertEquals($email, $response['body']['email']); + $this->assertEmpty($response['body']['password']); + + // Now set the password for that user (console-side) + $newPassword = 'NewPass123!'; + + $set = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + 'password' => $newPassword, + ]); + + $this->assertEquals(200, $set['headers']['status-code']); + $this->assertEquals($userId, $set['body']['$id']); + $this->assertNotEmpty($set['body']['password']); + } }