2023-03-08 18:30:01 +00:00
< ? php
use Appwrite\Event\Certificate ;
use Appwrite\Event\Delete ;
use Appwrite\Event\Event ;
use Appwrite\Extend\Exception ;
2025-04-08 14:48:50 +00:00
use Appwrite\Network\Validator\DNS ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\AuthType ;
use Appwrite\SDK\ContentType ;
use Appwrite\SDK\Method ;
use Appwrite\SDK\Response as SDKResponse ;
2023-03-08 18:30:01 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Rules ;
use Appwrite\Utopia\Response ;
2024-10-08 07:54:40 +00:00
use Utopia\App ;
2023-03-08 18:30:01 +00:00
use Utopia\Database\Database ;
use Utopia\Database\Document ;
2024-02-12 16:02:04 +00:00
use Utopia\Database\Exception\Query as QueryException ;
2023-03-08 18:30:01 +00:00
use Utopia\Database\Query ;
2024-10-17 05:41:24 +00:00
use Utopia\Database\Validator\Query\Cursor ;
2023-03-08 18:30:01 +00:00
use Utopia\Database\Validator\UID ;
use Utopia\Domains\Domain ;
2024-02-12 01:18:19 +00:00
use Utopia\Logger\Log ;
2024-04-01 11:08:46 +00:00
use Utopia\System\System ;
2025-04-08 14:48:50 +00:00
use Utopia\Validator\AnyOf ;
use Utopia\Validator\IP ;
2024-10-08 07:54:40 +00:00
use Utopia\Validator\Text ;
2023-03-08 18:30:01 +00:00
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/proxy/rules' )
2023-03-08 18:30:01 +00:00
-> groups ([ 'api' , 'proxy' ])
2024-09-03 16:22:30 +00:00
-> desc ( 'List rules' )
2023-03-08 18:30:01 +00:00
-> label ( 'scope' , 'rules.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'proxy' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'listRules' ,
description : '/docs/references/proxy/list-rules.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PROXY_RULE_LIST ,
)
]
))
2023-03-08 18:30:01 +00:00
-> 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 )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( array $queries , string $search , Response $response , Document $project , Database $dbForPlatform ) {
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2023-03-08 18:30:01 +00:00
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
2025-05-26 05:42:11 +00:00
$queries [] = Query :: equal ( 'projectInternalId' , [ $project -> getSequence ()]);
2023-03-08 18:30:01 +00:00
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
$cursor = \array_filter ( $queries , function ( $query ) {
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
});
2023-03-08 18:30:01 +00:00
$cursor = reset ( $cursor );
if ( $cursor ) {
/** @var Query $cursor */
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-03-08 18:30:01 +00:00
$ruleId = $cursor -> getValue ();
2024-12-12 10:30:26 +00:00
$cursorDocument = $dbForPlatform -> getDocument ( 'rules' , $ruleId );
2023-03-08 18:30:01 +00:00
if ( $cursorDocument -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Rule ' { $ruleId } ' for the 'cursor' value not found. " );
}
$cursor -> setValue ( $cursorDocument );
}
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2024-12-12 10:30:26 +00:00
$rules = $dbForPlatform -> find ( 'rules' , $queries );
2023-03-13 13:35:34 +00:00
foreach ( $rules as $rule ) {
2024-12-12 10:30:26 +00:00
$certificate = $dbForPlatform -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
2023-03-13 13:35:34 +00:00
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-06-08 15:24:27 +00:00
$rule -> setAttribute ( 'renewAt' , $certificate -> getAttribute ( 'renewDate' , '' ));
2023-03-13 13:35:34 +00:00
}
2023-03-08 18:30:01 +00:00
$response -> dynamic ( new Document ([
2023-03-13 13:35:34 +00:00
'rules' => $rules ,
2024-12-12 10:30:26 +00:00
'total' => $dbForPlatform -> count ( 'rules' , $filterQueries , APP_LIMIT_COUNT ),
2023-03-08 18:30:01 +00:00
]), Response :: MODEL_PROXY_RULE_LIST );
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/proxy/rules/:ruleId' )
2023-03-08 18:30:01 +00:00
-> groups ([ 'api' , 'proxy' ])
2024-09-03 16:22:30 +00:00
-> desc ( 'Get rule' )
2023-03-08 18:30:01 +00:00
-> label ( 'scope' , 'rules.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'proxy' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getRule' ,
description : '/docs/references/proxy/get-rule.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PROXY_RULE ,
)
]
))
2023-03-08 18:30:01 +00:00
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $ruleId , Response $response , Document $project , Database $dbForPlatform ) {
$rule = $dbForPlatform -> getDocument ( 'rules' , $ruleId );
2023-03-08 18:30:01 +00:00
2025-05-26 05:42:11 +00:00
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getSequence ()) {
2023-03-08 18:30:01 +00:00
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
2024-12-12 10:30:26 +00:00
$certificate = $dbForPlatform -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
2023-03-13 13:35:34 +00:00
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-06-08 15:24:27 +00:00
$rule -> setAttribute ( 'renewAt' , $certificate -> getAttribute ( 'renewDate' , '' ));
2023-03-13 13:35:34 +00:00
2023-03-08 18:30:01 +00:00
$response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
});
2024-10-08 07:54:40 +00:00
App :: delete ( '/v1/proxy/rules/:ruleId' )
2023-03-08 18:30:01 +00:00
-> groups ([ 'api' , 'proxy' ])
2024-09-03 16:22:30 +00:00
-> desc ( 'Delete rule' )
2023-03-08 18:30:01 +00:00
-> label ( 'scope' , 'rules.write' )
-> label ( 'event' , 'rules.[ruleId].delete' )
2023-07-13 11:42:04 +00:00
-> label ( 'audits.event' , 'rules.delete' )
2023-03-08 18:30:01 +00:00
-> label ( 'audits.resource' , 'rule/{request.ruleId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'proxy' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'deleteRule' ,
description : '/docs/references/proxy/delete-rule.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2023-03-08 18:30:01 +00:00
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2023-10-19 15:28:01 +00:00
-> inject ( 'queueForDeletes' )
-> inject ( 'queueForEvents' )
2024-12-12 10:30:26 +00:00
-> action ( function ( string $ruleId , Response $response , Document $project , Database $dbForPlatform , Delete $queueForDeletes , Event $queueForEvents ) {
$rule = $dbForPlatform -> getDocument ( 'rules' , $ruleId );
2023-03-08 18:30:01 +00:00
2025-05-26 05:42:11 +00:00
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getSequence ()) {
2023-03-08 18:30:01 +00:00
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
2024-12-12 10:30:26 +00:00
$dbForPlatform -> deleteDocument ( 'rules' , $rule -> getId ());
2023-03-08 18:30:01 +00:00
2023-10-19 15:28:01 +00:00
$queueForDeletes
2023-03-08 18:30:01 +00:00
-> setType ( DELETE_TYPE_DOCUMENT )
-> setDocument ( $rule );
2023-10-19 15:28:01 +00:00
$queueForEvents -> setParam ( 'ruleId' , $rule -> getId ());
2023-03-08 18:30:01 +00:00
$response -> noContent ();
});
2024-10-08 07:54:40 +00:00
App :: patch ( '/v1/proxy/rules/:ruleId/verification' )
-> desc ( 'Update rule verification status' )
2023-03-08 18:30:01 +00:00
-> groups ([ 'api' , 'proxy' ])
-> label ( 'scope' , 'rules.write' )
2023-03-10 12:20:24 +00:00
-> label ( 'event' , 'rules.[ruleId].update' )
-> label ( 'audits.event' , 'rule.update' )
-> label ( 'audits.resource' , 'rule/{response.$id}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'proxy' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'updateRuleVerification' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/proxy/update-rule-verification.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PROXY_RULE ,
)
]
))
2023-03-08 18:30:01 +00:00
-> param ( 'ruleId' , '' , new UID (), 'Rule ID.' )
-> inject ( 'response' )
2023-10-19 15:28:01 +00:00
-> inject ( 'queueForCertificates' )
-> inject ( 'queueForEvents' )
2023-03-08 18:30:01 +00:00
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2024-02-12 01:18:19 +00:00
-> inject ( 'log' )
2024-12-12 10:30:26 +00:00
-> action ( function ( string $ruleId , Response $response , Certificate $queueForCertificates , Event $queueForEvents , Document $project , Database $dbForPlatform , Log $log ) {
$rule = $dbForPlatform -> getDocument ( 'rules' , $ruleId );
2023-03-08 18:30:01 +00:00
2025-05-26 05:42:11 +00:00
if ( $rule -> isEmpty () || $rule -> getAttribute ( 'projectInternalId' ) !== $project -> getSequence ()) {
2023-03-08 18:30:01 +00:00
throw new Exception ( Exception :: RULE_NOT_FOUND );
}
2025-05-22 08:13:12 +00:00
$targetCNAME = null ;
switch ( $rule -> getAttribute ( 'type' , '' )) {
case 'api' :
// For example: fra.cloud.appwrite.io
$targetCNAME = new Domain ( System :: getEnv ( '_APP_DOMAIN_TARGET_CNAME' , '' ));
break ;
case 'redirect' :
// For example: appwrite.network
$targetCNAME = new Domain ( System :: getEnv ( '_APP_DOMAIN_SITES' , '' ));
break ;
case 'deployment' :
switch ( $rule -> getAttribute ( 'deploymentResourceType' , '' )) {
case 'function' :
// For example: fra.appwrite.run
$targetCNAME = new Domain ( System :: getEnv ( '_APP_DOMAIN_FUNCTIONS' , '' ));
break ;
case 'site' :
// For example: appwrite.network
$targetCNAME = new Domain ( System :: getEnv ( '_APP_DOMAIN_SITES' , '' ));
break ;
default :
break ;
}
// no break
default :
break ;
}
2025-04-08 14:48:50 +00:00
$validators = [];
2023-03-08 18:30:01 +00:00
2025-05-22 08:13:12 +00:00
if ( ! is_null ( $targetCNAME )) {
if ( $targetCNAME -> isKnown () && ! $targetCNAME -> isTest ()) {
$validators [] = new DNS ( $targetCNAME -> get (), DNS :: RECORD_CNAME );
}
2025-04-08 14:48:50 +00:00
}
2025-05-22 08:13:12 +00:00
2025-04-08 14:48:50 +00:00
if (( new IP ( IP :: V4 )) -> isValid ( System :: getEnv ( '_APP_DOMAIN_TARGET_A' , '' ))) {
$validators [] = new DNS ( System :: getEnv ( '_APP_DOMAIN_TARGET_A' , '' ), DNS :: RECORD_A );
}
if (( new IP ( IP :: V6 )) -> isValid ( System :: getEnv ( '_APP_DOMAIN_TARGET_AAAA' , '' ))) {
$validators [] = new DNS ( System :: getEnv ( '_APP_DOMAIN_TARGET_AAAA' , '' ), DNS :: RECORD_AAAA );
}
2025-08-03 20:03:47 +00:00
2025-04-08 14:48:50 +00:00
if ( empty ( $validators )) {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'At least one of domain targets environment variable must be configured.' );
2023-03-08 18:30:01 +00:00
}
if ( $rule -> getAttribute ( 'verification' ) === true ) {
return $response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
}
2025-04-08 14:48:50 +00:00
$validator = new AnyOf ( $validators , AnyOf :: TYPE_STRING );
2023-03-10 07:42:52 +00:00
$domain = new Domain ( $rule -> getAttribute ( 'domain' , '' ));
2023-03-08 18:30:01 +00:00
2024-02-12 01:18:19 +00:00
$validationStart = \microtime ( true );
2023-03-10 07:42:52 +00:00
if ( ! $validator -> isValid ( $domain -> get ())) {
2024-02-12 01:18:19 +00:00
$log -> addExtra ( 'dnsTiming' , \strval ( \microtime ( true ) - $validationStart ));
$log -> addTag ( 'dnsDomain' , $domain -> get ());
2025-04-08 14:48:50 +00:00
$errors = [];
foreach ( $validators as $validator ) {
if ( ! empty ( $validator -> getLogs ())) {
$errors [] = $validator -> getLogs ();
}
}
$error = \implode ( " \n " , $errors );
2024-02-12 01:18:19 +00:00
$log -> addExtra ( 'dnsResponse' , \is_array ( $error ) ? \json_encode ( $error ) : \strval ( $error ));
2023-03-08 18:30:01 +00:00
throw new Exception ( Exception :: RULE_VERIFICATION_FAILED );
}
2025-08-05 11:44:06 +00:00
// Ensure CAA won't block certificate issuance
if ( ! empty ( System :: getEnv ( '_APP_DOMAIN_TARGET_CAA' , '' ))) {
$validationStart = \microtime ( true );
$validator = new DNS ( System :: getEnv ( '_APP_DOMAIN_TARGET_CAA' , '' ), DNS :: RECORD_CAA );
if ( ! $validator -> isValid ( $domain -> get ())) {
$log -> addExtra ( 'dnsTimingCaa' , \strval ( \microtime ( true ) - $validationStart ));
$log -> addTag ( 'dnsDomain' , $domain -> get ());
$error = $validator -> getDescription ();
$log -> addExtra ( 'dnsResponse' , \is_array ( $error ) ? \json_encode ( $error ) : \strval ( $error ));
throw new Exception ( Exception :: RULE_VERIFICATION_FAILED , 'Domain verification failed because CAA records do not allow certainly.com to issue certificates.' );
}
}
2024-12-12 10:30:26 +00:00
$dbForPlatform -> updateDocument ( 'rules' , $rule -> getId (), $rule -> setAttribute ( 'status' , 'verifying' ));
2023-03-08 18:30:01 +00:00
// Issue a TLS certificate when domain is verified
2023-10-19 15:28:01 +00:00
$queueForCertificates
2023-03-08 18:30:01 +00:00
-> setDomain ( new Document ([
2025-05-22 07:58:48 +00:00
'domain' => $rule -> getAttribute ( 'domain' ),
'domainType' => $rule -> getAttribute ( 'deploymentResourceType' , $rule -> getAttribute ( 'type' )),
2023-03-08 18:30:01 +00:00
]))
-> trigger ();
2023-10-19 15:28:01 +00:00
$queueForEvents -> setParam ( 'ruleId' , $rule -> getId ());
2023-03-10 12:20:24 +00:00
2024-12-12 10:30:26 +00:00
$certificate = $dbForPlatform -> getDocument ( 'certificates' , $rule -> getAttribute ( 'certificateId' , '' ));
2023-03-13 13:35:34 +00:00
$rule -> setAttribute ( 'logs' , $certificate -> getAttribute ( 'logs' , '' ));
2023-03-08 18:30:01 +00:00
$response -> dynamic ( $rule , Response :: MODEL_PROXY_RULE );
2023-03-14 19:31:23 +00:00
});