Feat docker geo#10824
Conversation
commit: |
Security Scan Results for PRDocker Image Scan Results
Source Code Scan Results🎉 No vulnerabilities found! |
59eae60 to
cc05de0
Compare
✨ Benchmark resultsComparing
Per-scenario breakdown & investigation detailsMetrics below reflect the current branch (after). Δ P95 compares against the base.
Top API waits (after)
|
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (13)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
87-92: Remove the hard-coded “Temp fix for logs”.These static date filters will hide most logs and are already stale (Feb 2025). Gate behind a config flag or remove entirely.
Apply this diff to remove them:
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)
82-86: Delete the stale “Temp fix for logs” block.Static date window hides logs and should not ship.
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
101-105: Remove the hard-coded logs filter.Same stale date window; remove or gate via config.
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);app/controllers/api/users.php (1)
929-934: Drop the temporary logs date filter.Prevents expected logs from appearing; please remove or guard by env.
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);app/controllers/api/teams.php (3)
475-483: Roles whitelist isn’t applied (array_filter result ignored) + stray semicolon.array_filter doesn’t assign back to $roles; whitelist isn’t enforced for console. Remove the stray semicolon and apply the filter result.
- if ($project->getId() === 'console') { - ; + if ($project->getId() === 'console') { $roles = array_keys(Config::getParam('roles', [])); - array_filter($roles, function ($role) { - return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); - }); + $roles = array_values(array_filter($roles, function ($role) { + return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS], true); + })); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); }
1086-1095: Roles whitelist isn’t applied in updateMembership.Same bug here; enforce the filtered list.
if ($project->getId() === 'console') { $roles = array_keys(Config::getParam('roles', [])); - array_filter($roles, function ($role) { - return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); - }); + $roles = array_values(array_filter($roles, function ($role) { + return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS], true); + })); return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); }
1468-1472: Remove hard-coded “Temp fix for logs”.Date-gated OR filter skews results. Delete it or feature-gate behind a config flag.
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);app/controllers/api/account.php (6)
2913-2917: Remove hard-coded “Temp fix for logs”.Skews account logs. Delete or gate by config.
- // Temp fix for logs - $queries[] = Query::or([ - Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))), - Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))), - ]);
2189-2195: Email template device var duplicates brand; fallback to model.- 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',
2461-2468: OTP email: device var duplicates brand; fallback to model.- 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',
4931-4934: MFA challenge email: device var duplicates brand; fallback to model.- 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceModel'] ?? 'UNKNOWN',
656-665: Event sessionId set to last iterated session, not the current one.Move setParam into the matching branch and drop the post-loop call.
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { $session->setAttribute('current', true); @@ - // Use current session for events. - $queueForEvents - ->setPayload($response->output($session, Response::MODEL_SESSION)); + // Use current session for events. + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()) + ->setPayload($response->output($session, Response::MODEL_SESSION)); @@ - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId());Also applies to: 669-672
156-161: Year format token should be 'Y', not 'YYYY'.PHP DateTime uses single-letter tokens, and 'Y' is the correct token for a 4-digit year; 'YYYY' is not recognized and will be treated as literal text.
- 'year' => (new \DateTime())->format('YYYY'), + 'year' => (new \DateTime())->format('Y'),
♻️ Duplicate comments (3)
app/controllers/api/messaging.php (3)
2543-2545: Same geoRecord concerns as provider logsThis topic-logs endpoint uses
geoRecordin the same way as the provider logs (countryCode/countryNametaken from a single per-request geoRecord and applied to all log rows, with direct array indexing). The same semantic and null-safety issues apply here as in the/providers/:providerId/logscomment.Also applies to: 2601-2602
2959-2961: Same geoRecord concerns as provider logsSubscriber logs also inject a single
geoRecordand reuse it for all log entries, with direct array indexing. This has the same behavior and robustness issues described in the provider logs comment.Also applies to: 3017-3018
3752-3754: Same geoRecord concerns as provider logsMessage logs likewise apply one per-request
geoRecordto every log entry and accesscountryCode/countryNamewithout null-coalescing. Same semantic and null-safety issues as noted for provider logs.Also applies to: 3810-3811
🧹 Nitpick comments (11)
docker-compose.yml (1)
965-971: Consider production-readiness configurations for the geo service.The new
appwrite-geoservice is using version0.1.0and lacks several configurations present in other services:
- No restart policy: Other services use
restart: unless-stoppedfor resilience- No health checks: Unable to detect if the service is functioning properly
- No resource limits: Could consume unlimited resources
- Version 0.1.0: Very early version - verify this is production-ready
- Missing dependency: The main
appwriteservice doesn't listappwrite-geoindepends_onConsider applying this configuration pattern consistent with other services:
appwrite-geo: container_name: appwrite-geo image: appwrite/geo:0.1.0 + restart: unless-stopped + <<: *x-logging networks: - appwrite environment: - GEO_SECRET=$_APP_GEO_SECRETAlso, add the geo service as a dependency where needed:
# In the appwrite service depends_on: - mariadb - redis - appwrite-geosrc/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
77-77: Remove unused $locale parameter.The
$localeparameter is injected but never used in the action method. This adds unnecessary overhead to the dependency injection system.Remove the unused injection:
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('response') ->inject('dbForProject') - ->inject('locale') ->inject('geoRecord') ->callback($this->action(...));And update the method signature:
- public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, Locale $locale, array $geoRecord): void + public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, array $geoRecord): voidapp/init/resources.php (1)
717-741: Back-compat: expose countryName and safe defaults.Downstream code expects
countryName. Add alias and guard absent fields.$output = []; $output['ip'] = $ip; @@ - if (!empty($record)) { - $output['countryCode'] = $record['countryCode']; + if (!empty($record)) { + $output['countryCode'] = $record['countryCode'] ?? '--'; $output['country'] = $locale->getText('countries.' . strtolower($record['countryCode']), $locale->getText('locale.country.unknown')); $output['continent'] = $locale->getText('continents.' . strtolower($record['continentCode']), $locale->getText('locale.country.unknown')); - $output['continentCode'] = $record['continentCode']; - $output['eu'] = (\in_array($record['countryCode'], $eu)) ? true : false; + $output['continentCode'] = $record['continentCode'] ?? '--'; + $output['eu'] = (\in_array($record['countryCode'] ?? '', $eu)) ? true : false; @@ - $output['currency'] = $currency; + $output['currency'] = $currency; + // Alias for existing consumers + $output['countryName'] = $output['country']; } else { $output['countryCode'] = '--'; $output['country'] = $locale->getText('locale.country.unknown'); $output['continent'] = $locale->getText('locale.country.unknown'); $output['continentCode'] = '--'; $output['eu'] = false; - $output['currency'] = $currency; + $output['currency'] = $currency; + $output['countryName'] = $output['country']; }app/controllers/shared/api/auth.php (1)
39-45: Clarify denylist condition and normalize country codes.Use explicit checks and trim codes. Also guard missing geoRecord key.
- $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); - if (!empty($denylist && $project->getId() === 'console')) { - $countries = explode(',', $denylist); - $country = $geoRecord['countryCode'] ?? ''; + $denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', ''); + if ($project->getId() === 'console' && $denylist !== '') { + $countries = array_map(static fn($c) => strtoupper(trim($c)), explode(',', $denylist)); + $country = strtoupper($geoRecord['countryCode'] ?? ''); if (in_array($country, $countries)) { throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED); } }app/controllers/api/users.php (1)
2212-2215: Guard session countryCode assignment.Minor: add default to avoid notices if geoRecord is missing fields (defensive coding).
- 'countryCode' => $geoRecord['countryCode'], + 'countryCode' => $geoRecord['countryCode'] ?? '--',app/controllers/api/locale.php (1)
34-36: Expose countryName for callers relying on it.Returning geoRecord is fine, but many call sites expect
countryName. Consider adding it before responding.- $output = $geoRecord; + $output = $geoRecord; + $output['countryName'] = $output['country'] ?? $output['countryName'] ?? null;app/controllers/general.php (3)
59-60: Remove unused $utopia param from router signature.Not used inside
router(). Drop it or prefix with_to satisfy linters.-function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, array $geoRecord, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) +function router(Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, array $geoRecord, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)Note: Update all call sites accordingly.
380-387: Use geoRecord['eu'] directly.Slight simplification; avoids re-checking against config.
- if ($geoRecord) { - $eu = Config::getParam('locale-eu'); - - $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? ''; - $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? ''; - $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false'; - } + if ($geoRecord) { + $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? ''; + $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? ''; + $headers['x-appwrite-continent-eu'] = ($geoRecord['eu'] ?? false) ? 'true' : 'false'; + }
868-875: Lazy-inject geoRecord to avoid external calls on init/OPTIONS/robots/humans requests.Verification confirms geoRecord is injected in broad paths but underutilized:
- Line 852 (
App::init()for api/web groups): Passed to all downstream handlers regardless of need- Line 1123 (
App::options()): Only used conditionally in router() calls- Line 1441 (
/robots.txt): Only needed in else branch for router(); main path renders template without it- Line 1475 (
/humans.txt): Only needed in else branch for router(); main path renders template without itMove geoRecord injection into the router path only, or refactor to resolve it lazily within router() instead of at the entry points.
app/controllers/api/teams.php (1)
1512-1514: Per-log geo should not use the viewer’s geoRecord; also add null-coalescing.
- Use stored per-log geo (if available) or derive from each log’s IP; geoRecord reflects the current caller.
- Add safe defaults to avoid notices.
- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? ''; + $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';If per-log geo is desired, confirm product intent; we can wire a lookup using $log['ip'] against the geo service.
app/controllers/api/account.php (1)
2937-2939: Per-log geo should not use the viewer’s geoRecord; also add null-coalescing.Use stored per-log geo or derive from each log’s IP; add safe defaults meanwhile.
- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? ''; + $output[$i]['countryName'] = $geoRecord['countryName'] ?? '',
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
.env(1 hunks)app/config/collections/common.php(1 hunks)app/controllers/api/account.php(13 hunks)app/controllers/api/locale.php(1 hunks)app/controllers/api/messaging.php(8 hunks)app/controllers/api/teams.php(4 hunks)app/controllers/api/users.php(4 hunks)app/controllers/general.php(9 hunks)app/controllers/shared/api/auth.php(1 hunks)app/init/resources.php(2 hunks)composer.json(1 hunks)docker-compose.yml(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php(1 hunks)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php(3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-08T01:20:14.364Z
Learnt from: stnguyen90
Repo: appwrite/appwrite PR: 10119
File: app/controllers/api/account.php:1226-1232
Timestamp: 2025-07-08T01:20:14.364Z
Learning: In Appwrite, `_APP_DOMAIN` is a required environment variable that must always be set for the system to function properly.
Applied to files:
.env
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.
Applied to files:
app/controllers/api/messaging.phpapp/controllers/api/users.php
🧬 Code graph analysis (12)
app/controllers/shared/api/auth.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(77-151)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
action(73-138)
app/controllers/api/locale.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(78-156)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(78-156)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(77-151)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)
action(68-133)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(78-156)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(77-151)src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
action(73-138)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
app/controllers/api/teams.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(78-156)src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
action(73-138)
app/init/resources.php (1)
tests/e2e/Client.php (2)
Client(8-342)addHeader(161-166)
app/controllers/api/messaging.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
app/controllers/api/account.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
app/controllers/api/users.php (1)
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
app/controllers/general.php (4)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(78-156)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(77-151)src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
action(73-138)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(99-506)
🪛 dotenv-linter (4.0.0)
.env
[warning] 126-126: [UnorderedKey] The _APP_STATS_USAGE_DUAL_WRITING_DBS key should go before the _APP_STORAGE_ANTIVIRUS key
(UnorderedKey)
[warning] 127-127: [EndingBlankLine] No blank line at the end of the file
(EndingBlankLine)
[warning] 127-127: [UnorderedKey] The _APP_GEO_SECRET key should go before the _APP_GRAPHQL_MAX_BATCH_SIZE key
(UnorderedKey)
🪛 GitHub Actions: Tests
app/init/resources.php
[error] 685-685: Server error occurring during account creation (POST: /v1/account). HTTP 500 - general_unknown.
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php
78-78: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php
73-73: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php
68-68: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php
77-77: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
app/controllers/general.php
59-59: Avoid unused parameters such as '$utopia'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
- GitHub Check: Benchmark
- GitHub Check: E2E Shared Mode Service Test (Sites, Shared V1)
🔇 Additional comments (4)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1)
53-53: LGTM! Dependency injection updated consistently.The change from
inject('geodb')toinject('geoRecord')aligns with the broader migration to array-based geolocation data.src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1)
52-52: LGTM! Consistent with the geolocation refactoring.The dependency injection change is correct and aligns with the migration pattern.
composer.json (1)
61-61: Dependency is reproducible due to composer.lock pinning.The composer.lock file pins
utopia-php/fetchto a specific commit (570209894551027b28375024ceae87ed39c24b40), which ensures reproducible builds regardless of the branch name specified in composer.json. This is the standard Composer workflow for feature branches and development versions.While using a stable release would be preferable once available, the current setup with a pinned commit eliminates the reproducibility and stability risks initially flagged. No action is required.
app/init/resources.php (1)
690-699: I'll search the codebase to find information about the Utopia library and how it's being used.Based on my research, I found the official Utopia/Fetch library documentation. The Client class supports setConnectionTimeout() and setTimeout() methods for configuration, and the Response object has a json() method to decode response body.
However, the review comment contains some inaccuracies:
- setBaseUrl() does not exist – the fetch() method accepts url and method as direct parameters, not through a setBaseUrl() call
- Method name is setConnectionTimeout() – not "setConnectTimeout()" as suggested in the comment
- HTTP methods are strings – the method parameter is a string (like 'GET' or 'POST'), not a Client constant
Update the geo service client configuration to add timeouts and encode the IP parameter properly.
The Utopia\Fetch\Client API differs from the review suggestion:
- Use fetch(url: ..., method: ...) to pass the URL directly as a parameter instead of setBaseUrl()
- The correct method name is
setConnectionTimeout()(not setConnectTimeout)- HTTP methods are strings ('GET', 'POST', etc.), not constants
- The env var for the endpoint should still be env-configurable at fetch time
$client = new Client(); $client->addHeader('Authorization', 'Bearer ' . System::getEnv('_APP_GEO_SECRET', '')); + $client->setConnectionTimeout(1000)->setTimeout(1500); - $client->setBaseUrl('http://appwrite-geo/v1'); + $endpoint = System::getEnv('_APP_GEO_ENDPOINT', 'http://appwrite-geo/v1'); $record = null; try { - $record = $client->fetch("/ips/{$ip}", Client::METHOD_GET); + $record = $client->fetch($endpoint . '/ips/' . rawurlencode((string)$ip), 'GET'); $record = $record->json();
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
docker-compose.yml (1)
966-972: Add restart policy and logging configuration for consistency.The appwrite-geo service is missing a restart policy and logging configuration that other services in the stack use. For consistency and reliability, consider adding:
restart: unless-stopped(similar to other services like appwrite-console at line 224)<<: *x-logging(for consistent log rotation/management like other services)Apply this diff:
appwrite-geo: + <<: *x-logging container_name: appwrite-geo image: appwrite/geo:0.1.0 + restart: unless-stopped networks: - appwrite environment: - GEO_SECRET=$_APP_GEO_SECRETapp/init/resources.php (1)
686-694: Add timeout configuration and consider making the base URL configurable.The HTTP client lacks timeout configuration, which could cause requests to hang indefinitely if the geo service is slow or unresponsive. Additionally, the base URL is hardcoded to
http://appwrite-geo/v1, which works for Docker Compose but reduces flexibility.Consider these improvements:
- Add timeout configuration to the Client
- Make the base URL configurable via environment variable
Example:
$client = new Client(); $client->addHeader('Authorization', 'Bearer ' . System::getEnv('_APP_GEO_SECRET')); $client->setTimeout(5); // 5 second timeout $client->setBaseUrl(System::getEnv('_APP_GEO_URL', 'http://appwrite-geo/v1'));
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/init/resources.php(3 hunks)docker-compose.yml(2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-08T01:20:14.364Z
Learnt from: stnguyen90
Repo: appwrite/appwrite PR: 10119
File: app/controllers/api/account.php:1226-1232
Timestamp: 2025-07-08T01:20:14.364Z
Learning: In Appwrite, `_APP_DOMAIN` is a required environment variable that must always be set for the system to function properly.
Applied to files:
docker-compose.yml
🧬 Code graph analysis (1)
app/init/resources.php (1)
src/Appwrite/Extend/Exception.php (1)
Exception(7-464)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: E2E Service Test (Users)
- GitHub Check: E2E Service Test (Migrations)
- GitHub Check: E2E Service Test (Teams)
- GitHub Check: E2E Service Test (Realtime)
- GitHub Check: E2E Service Test (Locale)
- GitHub Check: E2E Service Test (Sites)
- GitHub Check: E2E Service Test (Projects)
- GitHub Check: E2E Service Test (Functions)
- GitHub Check: E2E Service Test (GraphQL)
- GitHub Check: E2E Service Test (Databases/Legacy)
- GitHub Check: E2E Service Test (Databases/TablesDB)
- GitHub Check: E2E Service Test (Console)
- GitHub Check: E2E Service Test (FunctionsSchedule)
- GitHub Check: E2E Service Test (Avatars)
- GitHub Check: E2E Service Test (Account)
- GitHub Check: E2E Service Test (Dev Keys)
- GitHub Check: E2E Service Test (Site Screenshots)
- GitHub Check: Unit Test
- GitHub Check: E2E General Test
- GitHub Check: scan
🔇 Additional comments (3)
app/init/resources.php (3)
28-28: LGTM - Imports correctly added.The MaxMind Reader and Utopia Fetch Client imports are now present and support the new geoRecord resource.
Also applies to: 44-44
695-708: LGTM - Error handling enables graceful fallback.The try-catch block appropriately handles failures from the geo service by logging errors and falling back to the local geodb. The exception thrown on line 701 is caught and logged rather than propagated, which allows the fallback mechanism to work correctly.
722-748: LGTM - Output construction handles all cases correctly.The output array construction properly handles both successful geo lookups and fallback scenarios with appropriate defaults. Currency lookup logic correctly searches through configured currencies, and locale translations have fallback values.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/init/resources.php (1)
710-720: Add safety checks for array access in geodb fallback.Lines 715-718 access nested array keys from
$dbRecordwithout verifying they exist. If the geodb returns data in an unexpected format, this will cause undefined index errors. This issue was previously flagged and remains unaddressed.Apply this diff to add safety checks:
if (empty($record)) { // Fallback $dbRecord = $geodb->get($ip); if ($dbRecord) { $record = []; - $record['countryCode'] = $dbRecord['country']['iso_code']; - $record['country'] = $dbRecord['country']['names']; - $record['continent'] = $dbRecord['continent']['names']; - $record['continentCode'] = $dbRecord['continent']['code']; + $record['countryCode'] = $dbRecord['country']['iso_code'] ?? '--'; + $record['country'] = $dbRecord['country']['names'] ?? []; + $record['continent'] = $dbRecord['continent']['names'] ?? []; + $record['continentCode'] = $dbRecord['continent']['code'] ?? '--'; } }
🧹 Nitpick comments (1)
app/init/resources.php (1)
697-702: Simplify error handling—avoid throw-catch for expected fallback.Throwing
Exception::GENERAL_SERVER_ERRORon non-200 responses only to catch it immediately and set$record = nullis unnecessarily complex. Since fallback to geodb is the intended behavior, simply check the status and skip the exception.Apply this diff:
$record = null; try { $record = $client->fetch("/ips/{$ip}", Client::METHOD_GET); - if ($record->getStatusCode() !== 200) { - $error = $record->json(); - throw new Exception(Exception::GENERAL_SERVER_ERROR, $error['message'] ?? 'Failed to fetch geo data'); - } - $record = $record->json(); + $record = ($record->getStatusCode() === 200) ? $record->json() : null; } catch (Throwable $th) { Console::error($th->getMessage()); Console::error($th->getTraceAsString()); $record = null; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/init/resources.php(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/init/resources.php (2)
tests/e2e/Client.php (2)
Client(8-342)addHeader(161-166)src/Appwrite/Extend/Exception.php (1)
Exception(7-464)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: E2E Service Test (Users)
- GitHub Check: E2E Service Test (Migrations)
- GitHub Check: E2E Service Test (VCS)
- GitHub Check: E2E Service Test (Tokens)
- GitHub Check: E2E Service Test (Storage)
- GitHub Check: E2E Service Test (Projects)
- GitHub Check: E2E Service Test (Health)
- GitHub Check: E2E Service Test (Sites)
- GitHub Check: E2E Service Test (Databases/Legacy)
- GitHub Check: E2E Service Test (Databases/TablesDB)
- GitHub Check: E2E Service Test (Locale)
- GitHub Check: E2E Service Test (Console)
- GitHub Check: E2E Service Test (FunctionsSchedule)
- GitHub Check: E2E Service Test (Avatars)
- GitHub Check: E2E Service Test (Account)
- GitHub Check: E2E Service Test (Site Screenshots)
- GitHub Check: E2E Service Test (Dev Keys)
- GitHub Check: Unit Test
- GitHub Check: E2E General Test
- GitHub Check: scan
🔇 Additional comments (1)
app/init/resources.php (1)
28-28: LGTM: Required imports added.The MaxMind Reader and Utopia Fetch Client imports are correctly added to support the new geoRecord resource functionality. The Reader import also resolves the previous review comment about the missing type hint.
Also applies to: 44-44
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
70-76: Guard geoRecord lookups and use Locale for missing country names
$geoRecordis indexed directly for both fields:$output[$i]['countryCode'] = $geoRecord['countryCode']; $output[$i]['countryName'] = $geoRecord['countryName'];If the geo service omits either key, this will emit undefined index notices;
$localeis also injected but unused.Consider tightening this to be defensive and to leverage the locale fallback:
- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $countryCode = $geoRecord['countryCode'] ?? '--'; + $countryName = $geoRecord['countryName'] + ?? $locale->getText('locale.country.unknown'); + + $output[$i]['countryCode'] = $countryCode; + $output[$i]['countryName'] = $countryName;This avoids notices when geo data is partial, gives a clearer UX for unknown locations, and makes the
$localeparameter meaningful.Also applies to: 79-80, 117-151
app/controllers/api/users.php (1)
2184-2193: Guard geoRecord when setting session countryCodeIn the users
createSessionroute, the session document is initialized with:'ip' => $request->getIP(), 'countryCode' => $geoRecord['countryCode'], 'expire' => $expire,If
geoRecordis empty or lackscountryCode, this will trigger an undefined index notice. Downstream code already tolerates unknown country codes via the locale lookup.Recommend guarding this with a default:
- 'ip' => $request->getIP(), - 'countryCode' => $geoRecord['countryCode'], + 'ip' => $request->getIP(), + 'countryCode' => $geoRecord['countryCode'] ?? '--',This keeps behavior intact when geo data is present and avoids runtime notices when it is not.
Also applies to: 2203-2219
♻️ Duplicate comments (7)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
144-145: Defensively accesscountryCode/countryNamefrom$geoRecord.Directly indexing
$geoRecord['countryCode']/['countryName']will emit notices if the geo service returns incomplete data. Mirror the defensive style used for OS/client/device fields and fall back to a safe default.- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? ''; + $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';app/config/collections/common.php (1)
870-1001: Previous review concerns still apply.The comprehensive issues raised in the earlier review remain unaddressed in the current code:
- Latitude/longitude precision:
size: 2for float fields provides only ~1.1km accuracy; standard geolocation requires 6-8 decimal places for precision.- Autonomous System Number data type: Stored as
Database::VAR_STRINGbut represents a numeric value (0–4,294,967,295); should useDatabase::VAR_INTEGER.- Missing indexes: None of the 12 new attributes have database indexes, which will cause slow queries if these fields are used for filtering or searching.
- Migration strategy: The deployment path for adding these attributes to existing session documents remains unclear.
Since you're in "Chill" mode, consider prioritizing items 1 and 2 for immediate correction, while items 3 and 4 can be evaluated based on actual query patterns and deployment procedures.
src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
95-99: Harden geoRecord access when computing EU headerThe new geoRecord injection and parameter wiring look fine, but this block is still brittle:
if (!empty($ip)) { if ($geoRecord) { $eu = Config::getParam('locale-eu'); $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? ''; $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? ''; $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false'; } }If
countryCodeis missing, thein_arraycall can trigger an undefined index notice. Prefer deriving locals with null‑coalescing and then using them:- $headers['x-appwrite-country-code'] = $geoRecord['countryCode'] ?? ''; - $headers['x-appwrite-continent-code'] = $geoRecord['continentCode'] ?? ''; - $headers['x-appwrite-continent-eu'] = (\in_array($geoRecord['countryCode'], $eu)) ? 'true' : 'false'; + $countryCode = $geoRecord['countryCode'] ?? ''; + $continentCode = $geoRecord['continentCode'] ?? ''; + + $headers['x-appwrite-country-code'] = $countryCode; + $headers['x-appwrite-continent-code'] = $continentCode; + $headers['x-appwrite-continent-eu'] = (!empty($countryCode) && \in_array($countryCode, $eu)) ? 'true' : 'false';This avoids undefined index notices and keeps behavior unchanged when geo data is present.
Also applies to: 118-121, 236-243
app/controllers/api/users.php (1)
908-916: Make user log geo fields defensive and use locale fallbackThe user‑logs route now injects
geoRecordand maps it into each log entry:$output[$i]['countryCode'] = $geoRecord['countryCode']; $output[$i]['countryName'] = $geoRecord['countryName'];If the geo service omits one of these keys you’ll get undefined index notices, and
$localeis currently unused.Align this with other endpoints by guarding the keys and using a sensible default:
- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $countryCode = $geoRecord['countryCode'] ?? '--'; + $countryName = $geoRecord['countryName'] + ?? $locale->getText('locale.country.unknown'); + + $output[$i]['countryCode'] = $countryCode; + $output[$i]['countryName'] = $countryName;This eliminates undefined index notices and provides a clear “unknown country” label when geo data is missing.
Also applies to: 941-974
app/controllers/api/account.php (1)
194-195: Guard$geoRecordaccess forcountryCode/countryNameacross sessions and logsSeveral places assume
$geoRecord['countryCode']/$geoRecord['countryName']always exist when creating sessions or enriching logs (token/magic/phone sessions via$createSession, email-password and anonymous sessions, OAuth2 sessions, and/v1/account/logs). GivengeoRecordis populated from an external geo service and other code already treats its keys as optional, this can easily produce undefined-index notices when geo lookup fails or the payload shape changes.Use a null‑coalescing fallback (or
isset) for these fields so session creation and log rendering remain robust even when geo data is missing.Example adjustments:
@@ function $createSession(...) - 'countryCode' => $geoRecord['countryCode'], + 'countryCode' => $geoRecord['countryCode'] ?? '', @@ createEmailPasswordSession - 'countryCode' => $geoRecord['countryCode'], + 'countryCode' => $geoRecord['countryCode'] ?? '', @@ createAnonymousSession - 'countryCode' => $geoRecord['countryCode'], + 'countryCode' => $geoRecord['countryCode'] ?? '', @@ OAuth2 redirect session creation - 'countryCode' => $geoRecord['countryCode'], + 'countryCode' => $geoRecord['countryCode'] ?? '', @@ /v1/account/logs action - $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? ''; + $output[$i]['countryName'] = $geoRecord['countryName'] ?? '';Also applies to: 234-235, 927-933, 966-967, 1064-1068, 1129-1130, 1207-1211, 1401-1404, 1806-1807, 2912-2915, 2947-2948
app/controllers/api/teams.php (1)
1206-1208: Guard$geoRecordwhen settingcountryCodeon membership-confirmation sessionIn the membership status update flow, the auto-created session writes
'countryCode' => $geoRecord['countryCode']without checking the key. If the geo service fails or returns a partial record, this will trigger undefined-index notices during a user-accept-invite flow.Use a safe fallback here as well.
@@ ->inject('project') - ->inject('geoRecord') + ->inject('geoRecord') @@ session payload - 'factors' => ['email'], - 'countryCode' => $geoRecord['countryCode'], + 'factors' => ['email'], + 'countryCode' => $geoRecord['countryCode'] ?? '',Also applies to: 1278-1279
app/controllers/api/messaging.php (1)
1145-1146: geoRecord in log endpoints: add null-safety and confirm per-request vs per-log geo semanticsAcross provider/topic/subscriber/message log endpoints you inject
array $geoRecordand then index['countryCode']/['countryName']directly for every log row. Two points:
- Null-safety
If the geo service fails or returns a partial record, those keys may be absent, leading to PHP notices. Other code (e.g.Functions/Http/Executions/Create) guards with??. Suggest at least:- $output[$i]['countryCode'] = $geoRecord['countryCode']; - $output[$i]['countryName'] = $geoRecord['countryName']; + $output[$i]['countryCode'] = $geoRecord['countryCode'] ?? null; + $output[$i]['countryName'] = $geoRecord['countryName'] ?? null;and apply the same pattern in the topic, subscriber, and message log handlers.
- Semantics of geo on logs
geoRecordappears to represent the current viewer’s IP, while each log entry has its own$log['ip']. With this change, all rows will show the same country for a given response, which is different from resolving each log’s IP (if that was the previous behavior). This is a potential audit/logging semantics regression; if logs are meant to reflect the geo of the event rather than the viewer, these endpoints probably need per-log geo (or persisted geo fields) instead ofgeoRecord.Can you confirm what the intended contract is for
countryCode/countryNameon these log APIs and whether the per-request geo is acceptable here?Also applies to: 1202-1203, 2546-2547, 2604-2605, 2961-2962, 3019-3020, 3757-3758, 3815-3816
🧹 Nitpick comments (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (2)
70-76: Geo DI wiring looks consistent; consider documentinggeoRecordshape.
->inject('geoRecord')aligns with the updatedaction()signature and matches the pattern used inDocuments\Logs\XList, so wiring looks correct. To help future maintainers (and DI providers), consider adding a short docblock onaction()describing the expected keys in$geoRecord(e.g.,countryCode,countryName).
78-78: Signature matches injection order; unused$localeis benign.The updated
action()signature correctly mirrors the parameter/injection order (response,dbForProject,locale,geoRecord,authorization), so DI should remain stable. The$localeparameter is currently unused; given it’s part of the injection contract, it’s reasonable to leave it as-is rather than removing it and risking a mismatch. If PHPMD noise becomes an issue, you can suppress this specific rule at the method level instead of altering the signature.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
app/config/collections/common.php(1 hunks)app/controllers/api/account.php(13 hunks)app/controllers/api/messaging.php(8 hunks)app/controllers/api/teams.php(4 hunks)app/controllers/api/users.php(4 hunks)app/controllers/general.php(9 hunks)app/controllers/shared/api/auth.php(1 hunks)app/init/resources.php(3 hunks)composer.json(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php(2 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php(1 hunks)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php(1 hunks)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- composer.json
- app/init/resources.php
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-23T08:06:38.889Z
Learnt from: abnegate
Repo: appwrite/appwrite PR: 10546
File: src/Appwrite/Platform/Workers/Migrations.php:144-148
Timestamp: 2025-10-23T08:06:38.889Z
Learning: In the Appwrite codebase, migration workers receive already-validated data from queued jobs. Query validation using Query::parseQueries() happens at the API endpoint level (with try-catch for QueryException) before jobs are queued, so workers in src/Appwrite/Platform/Workers/Migrations.php don't need redundant validation.
Applied to files:
app/controllers/api/users.phpapp/controllers/api/messaging.php
🧬 Code graph analysis (7)
app/controllers/general.php (5)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(78-152)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php (1)
action(73-138)src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Logs/XList.php (1)
action(68-133)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)
app/controllers/api/users.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
app/controllers/api/messaging.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(78-152)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
app/controllers/api/account.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(78-152)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
app/controllers/api/teams.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(78-152)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
app/controllers/shared/api/auth.php (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php
79-79: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
app/controllers/general.php
59-59: Avoid unused parameters such as '$utopia'. (undefined)
(UnusedFormalParameter)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php
78-78: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: scan
🔇 Additional comments (3)
src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Rows/Logs/XList.php (1)
53-53: Injection change verified as correct.The parent class
DocumentLogXListat line 79 has the updated action method signature witharray $geoRecordparameter, confirming the injection change fromgeodbtogeoRecordis properly aligned and valid.src/Appwrite/Platform/Modules/Databases/Http/TablesDB/Tables/Logs/XList.php (1)
48-55: Geo record injection wiring looks consistent
->inject('geoRecord')is correctly added betweenlocaleandauthorization, matching the expected action signature in the parent XList implementation.app/controllers/shared/api/auth.php (1)
32-47: Geo denylist integration is wired correctlyInjecting
geoRecordand reading$geoRecord['countryCode'] ?? ''for the console country denylist is consistent with the rest of the PR and avoids undefined index issues.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ions/Logs/XList.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
112-145: Critical: All log entries incorrectly share the same geo location.The
$geoRecordis request-scoped (injected once per request at line 78) and applied to all log entries in the loop, regardless of their individual IP addresses (line 128). Each log entry will show the samecountryCodeandcountryName, even though the logs may originate from different IP addresses.This is a data correctness issue: since each historical audit log stores its own
ipaddress, the geo information should be resolved per-log from each log's IP, not from the current request's geo location.The fix requires per-log geo resolution. Since no existing geo lookup pattern exists in the codebase for this purpose, you will need to either:
- Resolve geo information for each log's IP within the loop, or
- Implement batch geo lookup for all unique IPs in the result set before the loop
♻️ Duplicate comments (1)
app/controllers/api/account.php (1)
1401-1404: FixTYPE::EMAILtypo and guard geoRecord in OAuth2 session creationTwo issues in the OAuth2 redirect session block:
TYPE::EMAIL(Line 1805) is almost certainly a typo forType::EMAIL(matching the importedAppwrite\Auth\MFA\Typeused elsewhere). As written, this will cause an undefined-constant error at runtime when this path is hit.countryCodeis read as$geoRecord['countryCode']without a fallback, unlike other flows that use?? ''. If the geo service omits this key, you’ll get undefined-index notices. This was previously flagged and fixed, so this looks like a regression.Consider applying:
- 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks - 'countryCode' => $geoRecord['countryCode'], + 'factors' => [Type::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks + 'countryCode' => $geoRecord['countryCode'] ?? '',Also applies to: 1793-1808
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
app/controllers/api/account.php(13 hunks)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)
app/controllers/api/account.php (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
action(78-152)src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Logs/XList.php (1)
action(79-157)src/Appwrite/Platform/Modules/Functions/Http/Executions/Create.php (1)
action(101-507)
🪛 PHPMD (2.15.0)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php
78-78: Avoid unused parameters such as '$locale'. (undefined)
(UnusedFormalParameter)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: Setup & Build Appwrite Image
- GitHub Check: scan
🔇 Additional comments (3)
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Logs/XList.php (1)
144-145: LGTM! Defensive checks properly implemented.The null coalescing operators with empty string defaults correctly address the previous concern about undefined array key warnings when the geo service returns incomplete data.
app/controllers/api/account.php (2)
194-313: geoRecord wiring for token/magic/phone session flows looks consistentThe new
array $geoRecordparameter on$createSessionand the added->inject('geoRecord')calls for the/sessions/token,/sessions/magic-url, and/sessions/phoneroutes are ordered correctly and match the closure signature. UsingcountryCodefrom$geoRecord['countryCode'] ?? ''inside the session document provides a safe default when geo lookup fails, without changing expiry or permissions behavior.Also applies to: 1207-1211, 2548-2552, 2590-2594
927-933: Safe geoRecord usage in email-password and anonymous sessionsInjecting
geoRecordinto the email-password and anonymous session routes and populatingcountryCodevia$geoRecord['countryCode'] ?? ''is aligned with the rest of the file and prevents undefined-index notices if the geo service returns an empty or partial payload. The type-hintedarray $geoRecordparameters also keep the DI contract explicit.Also applies to: 966-967, 1064-1068, 1129-1130
The 1.9.x branch replaced the ollama service with appwrite-embedding, but the merge left a stray 'ollama: condition: service_started' entry in the appwrite service's depends_on. Compose rejected the project with 'service "appwrite" depends on undefined service "ollama"' and the appwrite container failed to start in CI.
The three "geo" fields on a session record are actually properties of
the connecting network, not the geographic location:
geoConnectionType -> connectionType (Cable/DSL, Cellular, ...)
geoUserType -> connectionUsageType (hosting, residential, ...)
geoOrganization -> connectionOrganization
Aligns with how db-ip / MaxMind GeoIP2 categorise the fields ("traits"
of the network, not the location). connectionUsageType also matches
db-ip's REST naming for the same field (their REST returns "usageType";
the MMDB binary calls it "user_type" — confusing because the values
are usage profiles, not user roles).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts: # .env # src/Appwrite/Platform/Modules/Teams/Http/Memberships/Status/Update.php
The Mails listener already filters on sessionAlerts, email presence, magic-url/email-token provider, and first-session count — the wrapper conditionals in the token-verify and OAuth session handlers were dead code that survived a merge conflict resolution. Drop them and rely on the listener as the single source of truth for alert eligibility.
- Usage.php: revert phpstan-consistent-constructor refactor (unrelated to docker-geo) - general.php: revert docblock whitespace tweak - Executions/Create.php: restore geo-empty guard to match general.php, use isEu() helper, drop stray double-parens
Match the placeholder pattern used by other secrets in .env (_APP_OPENSSL_KEY_V1, _APP_EXECUTOR_SECRET) so deployments that forget to override it fail loudly rather than sharing a publicly-known bearer token.
…ields on GeoRecord Restructure GeoRecord + getGeoForIp so the cached record holds raw geo data and locale-translated country/continent names resolve lazily against the current request locale. Extract the full session field set from the geo service response (latitude/longitude/timeZone/weatherCode/postalCode/ autonomousSystem*/connection*/isp) via dedicated getters so account.php callsites stop relying on ad-hoc getAttribute() lookups. Normalize country/continent codes to uppercase before the EU check so the lookup no longer depends on the geo API and locale-eu.php happening to agree on case. Add an isEmpty() override so geo-fencing guards in general.php and Executions/Create.php actually key off "geo data is available" instead of "the ArrayObject has any elements", which was always true.
…navailable When _APP_CONSOLE_COUNTRIES_DENYLIST is configured and the geo lookup returns an unknown country (service unreachable or IP not in database), GeoRecord::getCountryCode() returns '--'. That value is never a member of a real denylist, so the previous check silently let the request through and bypassed the region policy. Deny access when geo data is unavailable on the console project so the denylist behaves as a hard region gate rather than a best-effort filter.
The fail-closed check on unknown country broke intra-network traffic: local development, CI test runners, VPNs, and container-to-container requests all present private or reserved IPs that MaxMind cannot resolve, so every console request from those environments started returning 451. Restrict the fail-closed path to public routable IPs. Private and reserved ranges have no country to gate on, so the denylist is inapplicable and we fall through to the normal auth flow. Real client traffic still hits the fail-closed branch when geo data is missing.
…rvice Distinguish "geo service returned a well-formed response with unknown country" from "geo service could not be reached". The former is normal for private and documentation IPs and must not block console traffic; the latter is the security concern F5 called out, where an operator cannot prove the caller sits outside the denied set. Track lookup success on GeoRecord and only fail-closed when the service did not respond successfully. This restores the auth flow for local development and CI test IPs while still refusing access when geo is misconfigured or offline against a configured denylist.
…memory cache Swoole workers reuse the singleton getGeoForIp closure across all requests. Capturing the Locale reference at first resolution would lock every subsequent request to the first request's language, and the per-worker $cache grew unbounded for the life of the process. Pass Locale into the closure invocation instead of closing over it, and rely on the per-request geoRecord resource to cache within a request.
MaxMind returned all region codes uppercase; the GeoRecord introduced in this PR was returning countryCode lowercase while continentCode stayed uppercase. That silently broke the public /v1/locale response, the x-appwrite-country-code function header, and asymmetrically shaped GeoRecord's API. Return countryCode uppercase and normalize the console denylist comparison to uppercase on both sides. Internal locale lookup keys (countries.<code>) remain lowercase since that is the on-disk key.
…write Convert the appwrite service's depends_on block to the long form so we can require the geo container to pass its healthcheck (already defined) before appwrite boots, instead of the default service_started which races the geo listener and lets appwrite come up while lookups fail.
The long-form dict with condition: service_healthy on appwrite-geo
required the target to define a healthcheck (it doesn't) and broke
${_APP_DB_HOST:-mongodb} variable substitution in the dict key
position. Restore the short-form list dependency.
Previously getCountryCode() returned '--' when the raw code wasn't in Appwrite's locale translations dictionary, while isEmpty() checked the raw stored value — the mismatch let the console country denylist silently bypass any country not translated in Appwrite (Antarctica, recently-created ISO codes, etc.). Country code is a machine-readable identifier; locale translation is only relevant for getCountryName().

What does this PR do?
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
Checklist