Repository: appwrite/appwrite
File: app/controllers/general.php (PHP)

Code before:
                    $ctaUrl = '/console/project-' . $region . '-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
                    $exception->addCTA('Reload', '/');
                    $exception->addCTA('View logs', $url . $ctaUrl);
                    break;
            }
            throw $exception;
        }

        if ($type === 'function') {
            $permissions = $resource->getAttribute('execute');
            if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
                $exception = new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView);
                $exception->addCTA('View settings', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings');
                throw $exception;
            }
        }

        $headers = \array_merge([], $requestHeaders);
        $executionId = ID::unique();
        $headers['x-appwrite-execution-id'] = $executionId ?? '';
        $headers['x-appwrite-user-id'] = '';
        $headers['x-appwrite-country-code'] = '';
        $headers['x-appwrite-continent-code'] = '';
        $headers['x-appwrite-continent-eu'] = 'false';
        $headers['x-appwrite-client-ip'] = $request->getIP();

        $jwtExpiry = $resource->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts
        $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
        $jwtKey = $jwtObj->encode([
            'projectId' => $project->getId(),
            'scopes' => $resource->getAttribute('scopes', [])
        ]);
        $headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
        $headers['x-appwrite-trigger'] = 'http';
        $headers['x-appwrite-user-jwt'] = '';

        $ip = $headers['x-real-ip'] ?? '';
        if (!empty($ip)) {
            $record = $geodb->get($ip);

            if ($record) {
                $eu = Config::getParam('locale-eu');

                $headers['x-appwrite-country-code'] = $record['country']['iso_code'] ?? '';
                $headers['x-appwrite-continent-code'] = $record['continent']['code'] ?? '';
                $headers['x-appwrite-continent-eu'] = (\in_array($record['country']['iso_code'], $eu)) ? 'true' : 'false';
            }
        }

        $headersFiltered = [];
        foreach ($headers as $key => $value) {

Review comment (security):
Good point by CodeRabbit here, let's add client IP header to whitelist of request headers, so it can be seen in Console by developer.

It's not privacy issue as tracking IP is allowed for security reasons, and we anyway already store IP of sessions, so our compliance already cover it.

Code after:
                    $exception = new AppwriteException(AppwriteException::BUILD_NOT_READY, view: $errorView);
                    $ctaUrl = '/console/project-' . $region . '-' . $project->getId() . '/sites/site-' . $resource->getId() . '/deployments/deployment-' . $deployment->getId();
                    $exception->addCTA('Reload', '/');
                    $exception->addCTA('View logs', $url . $ctaUrl);
                    break;
            }
            throw $exception;
        }

        if ($type === 'function') {
            $permissions = $resource->getAttribute('execute');
            if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) {
                $exception = new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView);
                $exception->addCTA('View settings', $url . '/console/project-' . $project->getAttribute('region', 'default') . '-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings');
                throw $exception;
            }
        }

        $executionId = ID::unique();

        $headers = \array_merge([], $requestHeaders);
        $headers['x-appwrite-execution-id'] = $executionId ?? '';
        $headers['x-appwrite-user-id'] = '';
        $headers['x-appwrite-country-code'] = '';
        $headers['x-appwrite-continent-code'] = '';
        $headers['x-appwrite-continent-eu'] = 'false';
        $ip = $request->getIP();
        $headers['x-appwrite-client-ip'] = $ip;

        $jwtExpiry = $resource->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts
        $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
        $jwtKey = $jwtObj->encode([
            'projectId' => $project->getId(),
            'scopes' => $resource->getAttribute('scopes', [])
        ]);
        $headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
        $headers['x-appwrite-trigger'] = 'http';
        $headers['x-appwrite-user-jwt'] = '';

        if (!empty($ip)) {
            $record = $geodb->get($ip);

            if ($record) {
                $eu = Config::getParam('locale-eu');

                $headers['x-appwrite-country-code'] = $record['country']['iso_code'] ?? '';
                $headers['x-appwrite-continent-code'] = $record['continent']['code'] ?? '';
                $headers['x-appwrite-continent-eu'] = (\in_array($record['country']['iso_code'], $eu)) ? 'true' : 'false';
            }
        }