diff --git a/Dockerfile b/Dockerfile index d097edf0ca..d2cb1af9da 100755 --- a/Dockerfile +++ b/Dockerfile @@ -24,9 +24,9 @@ ENV _APP_VERSION=$VERSION \ _APP_HOME=https://appwrite.io RUN \ - if [ "$DEBUG" == "true" ]; then \ + if [ "$DEBUG" == "true" ]; then \ apk add boost boost-dev; \ - fi + fi WORKDIR /usr/src/code diff --git a/app/assets/dbip/dbip-country-lite-2024-09.mmdb b/app/assets/dbip/dbip-country-lite-2024-09.mmdb deleted file mode 100644 index 43d6bcdeea..0000000000 Binary files a/app/assets/dbip/dbip-country-lite-2024-09.mmdb and /dev/null differ diff --git a/app/assets/dbip/dbip-country-lite-2025-12.mmdb b/app/assets/dbip/dbip-country-lite-2025-12.mmdb new file mode 100644 index 0000000000..4ecabbf735 Binary files /dev/null and b/app/assets/dbip/dbip-country-lite-2025-12.mmdb differ diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 05156f1c45..1af157286c 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -681,7 +681,13 @@ App::post('/v1/storage/buckets/:bucketId/files') 'metadata' => $metadata, ]); - $file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc); + try { + $file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc); + } catch (DuplicateException) { + throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS); + } catch (NotFoundException) { + throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); + } } else { $file = $file ->setAttribute('$permissions', $permissions) @@ -731,6 +737,8 @@ App::post('/v1/storage/buckets/:bucketId/files') try { $file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc); + } catch (DuplicateException) { + throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS); } catch (NotFoundException) { throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND); } 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/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 01a0817b55..c424e6884f 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -1796,7 +1796,8 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor throw new Exception(Exception::INSTALLATION_NOT_FOUND); } - $repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [ + $repository = Authorization::skip(fn () => $dbForPlatform->findOne('repositories', [ + Query::equal('$id', [$repositoryId]), Query::equal('projectInternalId', [$project->getSequence()]) ])); diff --git a/app/init/registers.php b/app/init/registers.php index aecc34740a..be2009449e 100644 --- a/app/init/registers.php +++ b/app/init/registers.php @@ -372,7 +372,7 @@ $register->set('smtp', function () { return $mail; }); $register->set('geodb', function () { - return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.mmdb'); + return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2025-12.mmdb'); }); $register->set('passwordsDictionary', function () { $content = \file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords'); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index e3ca65fa97..34e0aee1ae 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -849,7 +849,7 @@ $image = $this->getParam('image', ''); - _APP_DB_PASS appwrite-assistant: - image: appwrite/assistant:0.8.3 + image: appwrite/assistant:0.8.4 container_name: appwrite-assistant <<: *x-logging restart: unless-stopped @@ -857,7 +857,7 @@ $image = $this->getParam('image', ''); - appwrite environment: - _APP_ASSISTANT_OPENAI_API_KEY - + appwrite-browser: image: appwrite/browser:0.3.2 container_name: appwrite-browser diff --git a/docker-compose.yml b/docker-compose.yml index 1bd56149ab..6cf8070691 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -951,7 +951,7 @@ services: appwrite-assistant: container_name: appwrite-assistant - image: appwrite/assistant:0.8.3 + image: appwrite/assistant:0.8.4 networks: - appwrite environment: diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 9f35932700..b217608395 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -55,7 +55,7 @@ trait AccountBase 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => 'console', - 'x-forwarded-for' => '103.152.127.250' // Test IP for denied access region + 'x-forwarded-for' => '31.6.14.220' // Test IP for denied access region ]), [ 'userId' => ID::unique(), 'email' => $email, 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']); + } }