Improve proxy API quality

This commit is contained in:
Matej Bačo
2026-05-04 11:48:02 +02:00
parent d8349544d2
commit cd6f5c64f0
6 changed files with 66 additions and 38 deletions
+10 -8
View File
@@ -286,6 +286,16 @@ return [
'category' => 'Messaging',
],
// Proxy
'rules.read' => [
'description' => 'Access to read proxy rules.',
'category' => 'Proxy',
],
'rules.write' => [
'description' => 'Access to create, update, and delete proxy rules.',
'category' => 'Proxy',
],
// Other
"webhooks.read" => [
"description" =>
@@ -339,12 +349,4 @@ return [
'description' => 'Access to create, update, and delete resources under VCS service.',
'category' => 'Other',
],
'rules.read' => [
'description' => 'Access to read proxy rules.',
'category' => 'Other',
],
'rules.write' => [
'description' => 'Access to create, update, and delete proxy rules.',
'category' => 'Other',
],
];
@@ -9,11 +9,13 @@ use Appwrite\Platform\Modules\Proxy\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
@@ -47,8 +49,10 @@ class Create extends Action
name: 'createAPIRule',
description: <<<EOT
Create a new proxy rule for serving Appwrite's API on custom domain.
Rule ID is automatically generated as MD5 hash of a rule domain for performane purposes.
EOT,
auth: [AuthType::ADMIN],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@@ -67,11 +71,21 @@ class Create extends Action
->inject('dbForPlatform')
->inject('platform')
->inject('log')
->inject('authorization')
->callback($this->action(...));
}
public function action(string $domain, Response $response, Document $project, Certificate $publisherForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform, Log $log)
{
public function action(
string $domain,
Response $response,
Document $project,
Certificate $publisherForCertificates,
Event $queueForEvents,
Database $dbForPlatform,
array $platform,
Log $log,
Authorization $authorization,
) {
$this->validateDomainRestrictions($domain, $platform);
// TODO: (@Meldiron) Remove after 1.7.x migration
@@ -108,7 +122,7 @@ class Create extends Action
}
try {
$rule = $dbForPlatform->createDocument('rules', $rule);
$rule = $authorization->skip(fn() => $dbForPlatform->createDocument('rules', $rule));
} catch (Duplicate $e) {
throw new Exception(Exception::RULE_ALREADY_EXISTS);
}
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
@@ -43,7 +44,7 @@ class Delete extends Action
description: <<<EOT
Delete a proxy rule by its unique ID.
EOT,
auth: [AuthType::ADMIN],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@@ -58,6 +59,7 @@ class Delete extends Action
->inject('dbForPlatform')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('authorization')
->callback($this->action(...));
}
@@ -67,15 +69,16 @@ class Delete extends Action
Document $project,
Database $dbForPlatform,
DeleteEvent $queueForDeletes,
Event $queueForEvents
Event $queueForEvents,
Authorization $authorization,
) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$dbForPlatform->deleteDocument('rules', $rule->getId());
$authorization->skip(fn() => $dbForPlatform->deleteDocument('rules', $rule->getId()));
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
@@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Scope\HTTP;
@@ -39,7 +40,7 @@ class Get extends Action
description: <<<EOT
Get a proxy rule by its unique ID.
EOT,
auth: [AuthType::ADMIN],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -51,6 +52,7 @@ class Get extends Action
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
}
@@ -58,15 +60,16 @@ class Get extends Action
string $ruleId,
Response $response,
Document $project,
Database $dbForPlatform
Database $dbForPlatform,
Authorization $authorization,
) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
}
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$certificate = $authorization->skip(fn() => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
// Give priority to certificate generation logs if present
if (!empty($certificate->getAttribute('logs', ''))) {
@@ -13,6 +13,7 @@ use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Logger\Log;
use Utopia\Platform\Scope\HTTP;
@@ -44,9 +45,9 @@ class Update extends Action
group: null,
name: 'updateRuleVerification',
description: <<<EOT
Retry getting verification process of a proxy rule. This endpoint triggers domain verification by checking DNS records (CNAME) against the configured target domain. If verification is successful, a TLS certificate will be automatically provisioned for the domain.
If not succeeded yet, retry verification process of a proxy rule domain. This endpoint triggers domain verification by checking DNS records. If verification is successful, a TLS certificate will be automatically provisioned for the domain asynchronously in the background.
EOT,
auth: [AuthType::ADMIN],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -61,6 +62,7 @@ class Update extends Action
->inject('project')
->inject('dbForPlatform')
->inject('log')
->inject('authorization')
->callback($this->action(...));
}
@@ -71,9 +73,10 @@ class Update extends Action
Event $queueForEvents,
Document $project,
Database $dbForPlatform,
Log $log
Log $log,
Authorization $authorization,
) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getSequence()) {
throw new Exception(Exception::RULE_NOT_FOUND);
@@ -90,22 +93,22 @@ class Update extends Action
try {
$this->verifyRule($rule, $log);
// Reset logs and status for the rule
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
$rule = $authorization->skip(fn() => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'logs' => '',
'status' => RULE_STATUS_CERTIFICATE_GENERATING,
]));
])));
$certificateId = $rule->getAttribute('certificateId', '');
// Reset logs for the associated certificate.
if (!empty($certificateId)) {
$certificate = $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
$certificate = $authorization->skip(fn() => $dbForPlatform->updateDocument('certificates', $certificateId, new Document([
'logs' => '',
]));
])));
}
} catch (Exception $err) {
$dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
$authorization->skip(fn() => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
'$updatedAt' => DateTime::now(),
]));
])));
throw $err;
}
@@ -13,6 +13,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
@@ -44,7 +45,7 @@ class XList extends Action
description: <<<EOT
Get a list of all the proxy rules. You can use the query params to filter your results.
EOT,
auth: [AuthType::ADMIN],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -53,21 +54,23 @@ class XList extends Action
]
))
->param('queries', [], new Rules(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Rules::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true, deprecated: true)
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
}
public function action(
array $queries,
bool $total,
string $search,
bool $includeTotal,
Response $response,
Document $project,
Database $dbForPlatform
Database $dbForPlatform,
Authorization $authorization,
) {
try {
$queries = Query::parseQueries($queries);
@@ -91,7 +94,7 @@ class XList extends Action
}
$ruleId = $cursor->getValue();
$cursorDocument = $dbForPlatform->getDocument('rules', $ruleId);
$cursorDocument = $authorization->skip(fn() => $dbForPlatform->getDocument('rules', $ruleId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
@@ -102,9 +105,9 @@ class XList extends Action
$filterQueries = Query::groupByType($queries)['filters'];
$rules = $dbForPlatform->find('rules', $queries);
$rules = $authorization->skip(fn() => $dbForPlatform->find('rules', $queries));
foreach ($rules as $rule) {
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
$certificate = $authorization->skip(fn() => $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', '')));
// Give priority to certificate generation logs if present
if (!empty($certificate->getAttribute('logs', ''))) {
@@ -116,7 +119,7 @@ class XList extends Action
$response->dynamic(new Document([
'rules' => $rules,
'total' => $includeTotal ? $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT) : 0,
'total' => $total ? $authorization->skip(fn() => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT)) : 0,
]), Response::MODEL_PROXY_RULE_LIST);
}
}