diff --git a/.env b/.env index 55ec662f24..c9a8b3a0b4 100644 --- a/.env +++ b/.env @@ -125,3 +125,4 @@ _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10 _APP_PROJECT_REGIONS=default _APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000 _APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main +_APP_TRUSTED_HEADERS= diff --git a/app/config/variables.php b/app/config/variables.php index 408be8d54e..653e959101 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -357,6 +357,15 @@ return [ 'required' => false, 'question' => '', 'filter' => '' + ], + [ + 'name' => '_APP_TRUSTED_HEADERS', + 'description' => 'This option allows you to set the list of trusted headers, the value is a comma‑separated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.', + 'introduction' => '1.8.0', + 'default' => 'x-forwarded-for', + 'required' => false, + 'question' => '', + 'filter' => '' ] ], ], diff --git a/src/Appwrite/Utopia/Request.php b/src/Appwrite/Utopia/Request.php index ce570d2af9..8e6b5d47b3 100644 --- a/src/Appwrite/Utopia/Request.php +++ b/src/Appwrite/Utopia/Request.php @@ -9,9 +9,12 @@ use Swoole\Http\Request as SwooleRequest; use Utopia\Database\Validator\Authorization; use Utopia\Route; use Utopia\Swoole\Request as UtopiaRequest; +use Utopia\System\System; class Request extends UtopiaRequest { + protected array $trustedIpHeaders = explode(",", System::getEnv('_APP_TRUSTED_HEADERS') ?? 'x-forwarded-for'); + /** * @var array */ diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 13b5015241..c678fb83cd 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -326,4 +326,38 @@ trait AccountBase $this->assertEquals($response['headers']['status-code'], 204); } + + public function testTrustedIpViaHeaders(): void + { + $email = uniqid() . 'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-forwarded-for' => '203.0.113.195', + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals($response['headers']['status-code'], 201); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-forwarded-for' => '203.0.113.195', + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + $this->assertEquals($response['headers']['status-code'], 201); + $this->assertEquals('203.0.113.195', $response['body']['clientIp'] ?? $response['body']['ip'] ?? ''); + } }