2019-05-09 18:54:39 +12:00
< ? php
2019-10-01 17:57:41 +13:00
2021-01-12 10:52:05 +13:00
use Appwrite\Auth\Auth ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Audit ;
2022-06-22 22:51:49 +12:00
use Appwrite\Event\Database as EventDatabase ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Delete ;
2022-04-04 18:30:07 +12:00
use Appwrite\Event\Event ;
2022-11-16 07:13:17 +13:00
use Appwrite\Event\Func ;
2022-05-26 23:51:08 +12:00
use Appwrite\Event\Mail ;
2023-12-07 23:25:19 +13:00
use Appwrite\Event\Messaging ;
2023-05-24 01:43:03 +12:00
use Appwrite\Extend\Exception ;
2023-10-25 20:39:59 +13:00
use Appwrite\Event\Usage ;
2021-06-30 23:36:58 +12:00
use Appwrite\Messaging\Adapter\Realtime ;
2022-05-26 23:51:08 +12:00
use Appwrite\Utopia\Response ;
use Appwrite\Utopia\Request ;
2020-06-29 05:31:21 +12:00
use Utopia\App ;
2019-11-30 07:23:29 +13:00
use Utopia\Abuse\Abuse ;
use Utopia\Abuse\Adapters\TimeLimit ;
2022-07-24 05:42:42 +12:00
use Utopia\Cache\Adapter\Filesystem ;
use Utopia\Cache\Cache ;
2022-05-26 23:51:08 +12:00
use Utopia\Database\Database ;
2022-08-31 20:52:55 +12:00
use Utopia\Database\DateTime ;
2021-10-08 04:35:17 +13:00
use Utopia\Database\Document ;
2022-01-19 00:05:04 +13:00
use Utopia\Database\Validator\Authorization ;
2024-01-24 22:11:35 +13:00
use MaxMind\Db\Reader ;
2019-11-30 07:23:29 +13:00
2022-08-17 00:28:30 +12:00
$parseLabel = function ( string $label , array $responsePayload , array $requestParams , Document $user ) {
preg_match_all ( '/{(.*?)}/' , $label , $matches );
foreach ( $matches [ 1 ] ? ? [] as $pos => $match ) {
$find = $matches [ 0 ][ $pos ];
$parts = explode ( '.' , $match );
if ( count ( $parts ) !== 2 ) {
2023-08-02 04:21:34 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , " The server encountered an error while parsing the label: $label . Please create an issue on GitHub to allow us to investigate further https://github.com/appwrite/appwrite/issues/new/choose " );
2022-08-17 00:28:30 +12:00
}
$namespace = $parts [ 0 ] ? ? '' ;
$replace = $parts [ 1 ] ? ? '' ;
$params = match ( $namespace ) {
'user' => ( array ) $user ,
'request' => $requestParams ,
default => $responsePayload ,
};
if ( array_key_exists ( $replace , $params )) {
$label = \str_replace ( $find , $params [ $replace ], $label );
}
}
return $label ;
};
2019-11-30 07:23:29 +13:00
2023-10-25 20:39:59 +13:00
$databaseListener = function ( string $event , Document $document , Document $project , Usage $queueForUsage , Database $dbForProject ) {
$value = 1 ;
2022-10-30 18:16:51 +13:00
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
2023-10-25 20:39:59 +13:00
$value = - 1 ;
2022-10-30 18:14:46 +13:00
}
2023-10-25 20:39:59 +13:00
switch ( true ) {
case $document -> getCollection () === 'teams' :
$queueForUsage
-> addMetric ( METRIC_TEAMS , $value ); // per project
2023-05-24 01:43:03 +12:00
break ;
2023-10-25 20:39:59 +13:00
case $document -> getCollection () === 'users' :
$queueForUsage
-> addMetric ( METRIC_USERS , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
$queueForUsage
-> addReduce ( $document );
}
2022-10-30 18:14:46 +13:00
break ;
2023-10-25 20:39:59 +13:00
case $document -> getCollection () === 'sessions' : // sessions
$queueForUsage
-> addMetric ( METRIC_SESSIONS , $value ); //per project
2022-10-30 18:14:46 +13:00
break ;
2023-10-25 20:39:59 +13:00
case $document -> getCollection () === 'databases' : // databases
$queueForUsage
-> addMetric ( METRIC_DATABASES , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
$queueForUsage
-> addReduce ( $document );
}
2023-05-24 01:43:03 +12:00
break ;
2023-10-25 20:39:59 +13:00
case str_starts_with ( $document -> getCollection (), 'database_' ) && ! str_contains ( $document -> getCollection (), 'collection' ) : //collections
$parts = explode ( '_' , $document -> getCollection ());
$databaseInternalId = $parts [ 1 ] ? ? 0 ;
$queueForUsage
-> addMetric ( METRIC_COLLECTIONS , $value ) // per project
-> addMetric ( str_replace ( '{databaseInternalId}' , $databaseInternalId , METRIC_DATABASE_ID_COLLECTIONS ), $value ) // per database
;
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
$queueForUsage
-> addReduce ( $document );
2023-10-16 06:41:09 +13:00
}
2023-05-24 01:43:03 +12:00
break ;
2023-10-25 20:39:59 +13:00
case str_starts_with ( $document -> getCollection (), 'database_' ) && str_contains ( $document -> getCollection (), '_collection_' ) : //documents
$parts = explode ( '_' , $document -> getCollection ());
$databaseInternalId = $parts [ 1 ] ? ? 0 ;
$collectionInternalId = $parts [ 3 ] ? ? 0 ;
$queueForUsage
-> addMetric ( METRIC_DOCUMENTS , $value ) // per project
-> addMetric ( str_replace ( '{databaseInternalId}' , $databaseInternalId , METRIC_DATABASE_ID_DOCUMENTS ), $value ) // per database
-> addMetric ( str_replace ([ '{databaseInternalId}' , '{collectionInternalId}' ], [ $databaseInternalId , $collectionInternalId ], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS ), $value ); // per collection
break ;
case $document -> getCollection () === 'buckets' : //buckets
$queueForUsage
-> addMetric ( METRIC_BUCKETS , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
$queueForUsage
-> addReduce ( $document );
}
break ;
case str_starts_with ( $document -> getCollection (), 'bucket_' ) : // files
$parts = explode ( '_' , $document -> getCollection ());
$bucketInternalId = $parts [ 1 ];
$queueForUsage
-> addMetric ( METRIC_FILES , $value ) // per project
-> addMetric ( METRIC_FILES_STORAGE , $document -> getAttribute ( 'sizeOriginal' ) * $value ) // per project
-> addMetric ( str_replace ( '{bucketInternalId}' , $bucketInternalId , METRIC_BUCKET_ID_FILES ), $value ) // per bucket
-> addMetric ( str_replace ( '{bucketInternalId}' , $bucketInternalId , METRIC_BUCKET_ID_FILES_STORAGE ), $document -> getAttribute ( 'sizeOriginal' ) * $value ); // per bucket
break ;
case $document -> getCollection () === 'functions' :
$queueForUsage
-> addMetric ( METRIC_FUNCTIONS , $value ); // per project
if ( $event === Database :: EVENT_DOCUMENT_DELETE ) {
$queueForUsage
-> addReduce ( $document );
2023-10-16 06:41:09 +13:00
}
2023-05-24 01:43:03 +12:00
break ;
2023-10-25 20:39:59 +13:00
case $document -> getCollection () === 'deployments' :
$queueForUsage
-> addMetric ( METRIC_DEPLOYMENTS , $value ) // per project
-> addMetric ( METRIC_DEPLOYMENTS_STORAGE , $document -> getAttribute ( 'size' ) * $value ) // per project
-> addMetric ( str_replace ([ '{resourceType}' , '{resourceInternalId}' ], [ $document -> getAttribute ( 'resourceType' ), $document -> getAttribute ( 'resourceInternalId' )], METRIC_FUNCTION_ID_DEPLOYMENTS ), $value ) // per function
-> addMetric ( str_replace ([ '{resourceType}' , '{resourceInternalId}' ], [ $document -> getAttribute ( 'resourceType' ), $document -> getAttribute ( 'resourceInternalId' )], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE ), $document -> getAttribute ( 'size' ) * $value );
break ;
default :
break ;
2022-10-30 18:14:46 +13:00
}
};
2022-07-22 18:00:42 +12:00
App :: init ()
2022-08-02 13:10:48 +12:00
-> groups ([ 'api' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'user' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
2023-12-07 23:25:19 +13:00
-> inject ( 'queueForMessaging' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForAudits' )
-> inject ( 'queueForDeletes' )
-> inject ( 'queueForDatabase' )
2024-01-24 23:24:59 +13:00
-> inject ( 'queueForUsage' )
2022-07-22 18:00:42 +12:00
-> inject ( 'dbForProject' )
-> inject ( 'mode' )
2024-01-24 23:24:59 +13:00
-> action ( function ( App $utopia , Request $request , Response $response , Document $project , Document $user , Event $queueForEvents , Messaging $queueForMessaging , Audit $queueForAudits , Delete $queueForDeletes , EventDatabase $queueForDatabase , Usage $queueForUsage , Database $dbForProject , string $mode ) use ( $databaseListener ) {
2022-07-22 18:00:42 +12:00
2023-02-20 00:04:12 +13:00
$route = $utopia -> getRoute ();
2022-07-22 18:00:42 +12:00
if ( $project -> isEmpty () && $route -> getLabel ( 'abuse-limit' , 0 ) > 0 ) { // Abuse limit requires an active project scope
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: PROJECT_UNKNOWN );
2021-06-24 03:11:23 +12:00
}
2020-06-18 08:08:55 +12:00
2022-08-19 16:20:19 +12:00
/*
2022-07-22 18:00:42 +12:00
* Abuse Check
*/
2022-08-12 11:53:52 +12:00
$abuseKeyLabel = $route -> getLabel ( 'abuse-key' , 'url:{url},ip:{ip}' );
$timeLimitArray = [];
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$abuseKeyLabel = ( ! is_array ( $abuseKeyLabel )) ? [ $abuseKeyLabel ] : $abuseKeyLabel ;
2021-06-07 17:17:29 +12:00
2022-08-12 11:53:52 +12:00
foreach ( $abuseKeyLabel as $abuseKey ) {
2023-11-17 06:34:38 +13:00
$start = $request -> getContentRangeStart ();
$end = $request -> getContentRangeEnd ();
2022-08-12 11:53:52 +12:00
$timeLimit = new TimeLimit ( $abuseKey , $route -> getLabel ( 'abuse-limit' , 0 ), $route -> getLabel ( 'abuse-time' , 3600 ), $dbForProject );
$timeLimit
2023-05-24 01:43:03 +12:00
-> setParam ( '{userId}' , $user -> getId ())
-> setParam ( '{userAgent}' , $request -> getUserAgent ( '' ))
-> setParam ( '{ip}' , $request -> getIP ())
-> setParam ( '{url}' , $request -> getHostname () . $route -> getPath ())
2023-11-17 06:34:38 +13:00
-> setParam ( '{method}' , $request -> getMethod ())
-> setParam ( '{chunkId}' , ( int ) ( $start / ( $end + 1 - $start )));
2022-08-12 11:53:52 +12:00
$timeLimitArray [] = $timeLimit ;
}
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$closestLimit = null ;
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$roles = Authorization :: getRoles ();
$isPrivilegedUser = Auth :: isPrivilegedUser ( $roles );
$isAppUser = Auth :: isAppUser ( $roles );
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
foreach ( $timeLimitArray as $timeLimit ) {
foreach ( $request -> getParams () as $key => $value ) { // Set request params as potential abuse keys
if ( ! empty ( $value )) {
$timeLimit -> setParam ( '{param-' . $key . '}' , ( \is_array ( $value )) ? \json_encode ( $value ) : $value );
}
2021-11-09 23:21:24 +13:00
}
2021-06-07 17:17:29 +12:00
2022-07-22 18:00:42 +12:00
$abuse = new Abuse ( $timeLimit );
2022-08-13 15:21:50 +12:00
$remaining = $timeLimit -> remaining ();
$limit = $timeLimit -> limit ();
2022-08-31 20:52:55 +12:00
$time = ( new \DateTime ( $timeLimit -> time ())) -> getTimestamp () + $route -> getLabel ( 'abuse-time' , 3600 );
2022-07-22 18:00:42 +12:00
2022-08-13 15:21:50 +12:00
if ( $limit && ( $remaining < $closestLimit || is_null ( $closestLimit ))) {
$closestLimit = $remaining ;
2022-07-22 18:00:42 +12:00
$response
2022-08-13 15:21:50 +12:00
-> addHeader ( 'X-RateLimit-Limit' , $limit )
-> addHeader ( 'X-RateLimit-Remaining' , $remaining )
-> addHeader ( 'X-RateLimit-Reset' , $time )
2022-07-22 18:00:42 +12:00
;
2021-03-01 07:36:13 +13:00
}
2021-06-07 17:17:29 +12:00
2022-08-31 15:50:53 +12:00
$enabled = App :: getEnv ( '_APP_OPTIONS_ABUSE' , 'enabled' ) !== 'disabled' ;
2022-07-22 18:00:42 +12:00
if (
2022-08-31 15:50:53 +12:00
$enabled // Abuse is enabled
&& ! $isAppUser // User is not API key
&& ! $isPrivilegedUser // User is not an admin
&& $abuse -> check () // Route is rate-limited
) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED );
2021-03-01 07:36:13 +13:00
}
2021-11-09 23:21:24 +13:00
}
2021-01-06 01:22:20 +13:00
2022-11-16 18:30:57 +13:00
/*
* Background Jobs
*/
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-07-22 18:00:42 +12:00
-> setEvent ( $route -> getLabel ( 'event' , '' ))
-> setProject ( $project )
2022-08-12 01:47:53 +12:00
-> setUser ( $user );
2022-04-04 18:30:07 +12:00
2023-12-07 23:25:19 +13:00
$queueForMessaging
-> setProject ( $project );
2022-12-21 05:11:30 +13:00
$queueForAudits
2022-07-22 18:00:42 +12:00
-> setMode ( $mode )
-> setUserAgent ( $request -> getUserAgent ( '' ))
-> setIP ( $request -> getIP ())
2022-09-04 20:23:24 +12:00
-> setEvent ( $route -> getLabel ( 'audits.event' , '' ))
2022-07-22 18:00:42 +12:00
-> setProject ( $project )
2022-08-12 01:47:53 +12:00
-> setUser ( $user );
2021-03-01 07:36:13 +13:00
2022-07-22 18:00:42 +12:00
2022-12-21 05:11:30 +13:00
$queueForDeletes -> setProject ( $project );
$queueForDatabase -> setProject ( $project );
2022-08-09 23:57:33 +12:00
2023-10-25 20:39:59 +13:00
$dbForProject
-> on ( Database :: EVENT_DOCUMENT_CREATE , 'calculate-usage' , fn ( $event , $document ) => $databaseListener ( $event , $document , $project , $queueForUsage , $dbForProject ))
-> on ( Database :: EVENT_DOCUMENT_DELETE , 'calculate-usage' , fn ( $event , $document ) => $databaseListener ( $event , $document , $project , $queueForUsage , $dbForProject ))
;
2022-10-17 20:10:18 +13:00
2022-08-09 23:57:33 +12:00
$useCache = $route -> getLabel ( 'cache' , false );
2022-08-10 01:43:37 +12:00
2022-08-09 23:57:33 +12:00
if ( $useCache ) {
2023-09-06 03:29:11 +12:00
$key = md5 ( $request -> getURI () . implode ( '*' , $request -> getParams ()) . '*' . APP_CACHE_BUSTER );
2022-08-16 01:55:11 +12:00
$cache = new Cache (
new Filesystem ( APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project -> getId ())
);
2022-08-09 23:57:33 +12:00
$timestamp = 60 * 60 * 24 * 30 ;
$data = $cache -> load ( $key , $timestamp );
2022-11-10 23:08:01 +13:00
2022-08-09 23:57:33 +12:00
if ( ! empty ( $data )) {
2022-08-10 01:43:37 +12:00
$data = json_decode ( $data , true );
2022-11-10 23:08:01 +13:00
$parts = explode ( '/' , $data [ 'resourceType' ]);
$type = $parts [ 0 ] ? ? null ;
if ( $type === 'bucket' ) {
$bucketId = $parts [ 1 ] ? ? null ;
$bucket = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'buckets' , $bucketId ));
2023-08-17 09:58:25 +12:00
$isAPIKey = Auth :: isAppUser ( Authorization :: getRoles ());
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
if ( $bucket -> isEmpty () || ( ! $bucket -> getAttribute ( 'enabled' ) && ! $isAPIKey && ! $isPrivilegedUser )) {
2022-11-10 23:08:01 +13:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
}
$fileSecurity = $bucket -> getAttribute ( 'fileSecurity' , false );
$validator = new Authorization ( Database :: PERMISSION_READ );
$valid = $validator -> isValid ( $bucket -> getRead ());
if ( ! $fileSecurity && ! $valid ) {
throw new Exception ( Exception :: USER_UNAUTHORIZED );
}
$parts = explode ( '/' , $data [ 'resource' ]);
$fileId = $parts [ 1 ] ? ? null ;
if ( $fileSecurity && ! $valid ) {
$file = $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId );
} else {
$file = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'bucket_' . $bucket -> getInternalId (), $fileId ));
}
if ( $file -> isEmpty ()) {
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
}
}
2022-08-10 01:43:37 +12:00
$response
-> addHeader ( 'Expires' , \date ( 'D, d M Y H:i:s' , \time () + $timestamp ) . ' GMT' )
-> addHeader ( 'X-Appwrite-Cache' , 'hit' )
2022-11-10 23:08:01 +13:00
-> setContentType ( $data [ 'contentType' ])
2022-08-10 01:43:37 +12:00
-> send ( base64_decode ( $data [ 'payload' ]))
;
} else {
$response -> addHeader ( 'X-Appwrite-Cache' , 'miss' );
}
}
2022-07-22 18:00:42 +12:00
});
App :: init ()
2022-08-02 13:10:48 +12:00
-> groups ([ 'auth' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'project' )
2024-02-02 01:10:41 +13:00
-> inject ( 'geodb' )
-> action ( function ( App $utopia , Request $request , Document $project , Reader $geodb ) {
2024-02-03 03:31:54 +13:00
$denylist = App :: getEnv ( '_APP_CONSOLE_COUNTRIES_DENYLIST' , '' );
if ( ! empty ( $denylist ) && $project -> getId () === 'console' ) {
2024-02-02 01:10:41 +13:00
$countries = explode ( ',' , $denylist );
$record = $geodb -> get ( $request -> getIP ()) ? ? [];
$country = $record [ 'country' ][ 'iso_code' ] ? ? '' ;
if ( in_array ( $country , $countries )) {
throw new Exception ( Exception :: GENERAL_REGION_ACCESS_DENIED );
}
}
2022-07-22 18:00:42 +12:00
2023-02-20 00:04:12 +13:00
$route = $utopia -> getRoute ();
2022-07-22 18:00:42 +12:00
$isPrivilegedUser = Auth :: isPrivilegedUser ( Authorization :: getRoles ());
$isAppUser = Auth :: isAppUser ( Authorization :: getRoles ());
if ( $isAppUser || $isPrivilegedUser ) { // Skip limits for app and console devs
return ;
}
2021-03-01 07:36:13 +13:00
2022-07-22 18:00:42 +12:00
$auths = $project -> getAttribute ( 'auths' , []);
switch ( $route -> getLabel ( 'auth.type' , '' )) {
case 'emailPassword' :
if (( $auths [ 'emailPassword' ] ? ? true ) === false ) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Email / Password authentication is disabled for this project' );
2022-07-22 18:00:42 +12:00
}
break ;
case 'magic-url' :
if ( $project -> getAttribute ( 'usersAuthMagicURL' , true ) === false ) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Magic URL authentication is disabled for this project' );
2022-07-22 18:00:42 +12:00
}
break ;
case 'anonymous' :
if (( $auths [ 'anonymous' ] ? ? true ) === false ) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Anonymous authentication is disabled for this project' );
2022-07-22 18:00:42 +12:00
}
break ;
case 'invites' :
if (( $auths [ 'invites' ] ? ? true ) === false ) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Invites authentication is disabled for this project' );
2022-07-22 18:00:42 +12:00
}
break ;
case 'jwt' :
if (( $auths [ 'JWT' ] ? ? true ) === false ) {
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'JWT authentication is disabled for this project' );
2022-07-22 18:00:42 +12:00
}
break ;
2023-10-26 01:20:29 +13:00
case 'phone' :
if (( $auths [ 'phone' ] ? ? true ) === false ) {
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Phone authentication is disabled for this project' );
}
break ;
2022-07-22 18:00:42 +12:00
2024-02-02 21:33:20 +13:00
case 'email-otp' :
if (( $auths [ 'emailOTP' ] ? ? true ) === false ) {
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Email OTP authentication is disabled for this project' );
}
break ;
2022-07-22 18:00:42 +12:00
default :
2022-08-09 02:44:07 +12:00
throw new Exception ( Exception :: USER_AUTH_METHOD_UNSUPPORTED , 'Unsupported authentication route' );
2022-07-22 18:00:42 +12:00
break ;
2022-08-12 11:53:52 +12:00
}
2022-07-22 18:00:42 +12:00
});
2023-05-24 01:43:03 +12:00
/**
* Limit user session
*
* Delete older sessions if the number of sessions have crossed
* the session limit set for the project
*/
App :: shutdown ()
-> groups ([ 'session' ])
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'dbForProject' )
-> action ( function ( App $utopia , Request $request , Response $response , Document $project , Database $dbForProject ) {
$sessionLimit = $project -> getAttribute ( 'auths' , [])[ 'maxSessions' ] ? ? APP_LIMIT_USER_SESSIONS_DEFAULT ;
$session = $response -> getPayload ();
$userId = $session [ 'userId' ] ? ? '' ;
if ( empty ( $userId )) {
return ;
}
$user = $dbForProject -> getDocument ( 'users' , $userId );
if ( $user -> isEmpty ()) {
return ;
}
$sessions = $user -> getAttribute ( 'sessions' , []);
$count = \count ( $sessions );
if ( $count <= $sessionLimit ) {
return ;
}
for ( $i = 0 ; $i < ( $count - $sessionLimit ); $i ++ ) {
$session = array_shift ( $sessions );
$dbForProject -> deleteDocument ( 'sessions' , $session -> getId ());
}
2023-12-15 02:32:06 +13:00
$dbForProject -> purgeCachedDocument ( 'users' , $userId );
2023-05-24 01:43:03 +12:00
});
2022-07-22 18:00:42 +12:00
App :: shutdown ()
2022-08-02 13:10:48 +12:00
-> groups ([ 'api' ])
2022-07-22 18:00:42 +12:00
-> inject ( 'utopia' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
2023-07-07 12:12:39 +12:00
-> inject ( 'user' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForAudits' )
2023-10-25 20:39:59 +13:00
-> inject ( 'queueForUsage' )
2022-12-21 05:11:30 +13:00
-> inject ( 'queueForDeletes' )
-> inject ( 'queueForDatabase' )
2022-07-22 18:00:42 +12:00
-> inject ( 'dbForProject' )
2022-11-16 23:33:11 +13:00
-> inject ( 'queueForFunctions' )
2022-12-15 20:56:06 +13:00
-> inject ( 'mode' )
2023-07-07 12:12:39 +12:00
-> inject ( 'dbForConsole' )
2023-10-25 20:39:59 +13:00
-> action ( function ( App $utopia , Request $request , Response $response , Document $project , Document $user , Event $queueForEvents , Audit $queueForAudits , Usage $queueForUsage , Delete $queueForDeletes , EventDatabase $queueForDatabase , Database $dbForProject , Func $queueForFunctions , string $mode , Database $dbForConsole ) use ( $parseLabel ) {
2022-08-09 23:57:33 +12:00
2022-08-08 03:09:37 +12:00
$responsePayload = $response -> getPayload ();
2022-07-22 18:00:42 +12:00
2022-12-21 05:11:30 +13:00
if ( ! empty ( $queueForEvents -> getEvent ())) {
if ( empty ( $queueForEvents -> getPayload ())) {
$queueForEvents -> setPayload ( $responsePayload );
2021-03-01 07:36:13 +13:00
}
2023-06-02 15:54:34 +12:00
2022-07-22 18:00:42 +12:00
/**
* Trigger functions .
*/
2022-11-16 23:33:11 +13:00
$queueForFunctions
2022-12-21 05:11:30 +13:00
-> from ( $queueForEvents )
2022-07-22 18:00:42 +12:00
-> trigger ();
/**
* Trigger webhooks .
*/
2022-12-21 05:11:30 +13:00
$queueForEvents
2022-07-22 18:00:42 +12:00
-> setClass ( Event :: WEBHOOK_CLASS_NAME )
-> setQueue ( Event :: WEBHOOK_QUEUE_NAME )
-> trigger ();
/**
* Trigger realtime .
*/
if ( $project -> getId () !== 'console' ) {
2022-12-21 05:11:30 +13:00
$allEvents = Event :: generateEvents ( $queueForEvents -> getEvent (), $queueForEvents -> getParams ());
$payload = new Document ( $queueForEvents -> getPayload ());
2022-07-22 18:00:42 +12:00
2022-12-21 05:11:30 +13:00
$db = $queueForEvents -> getContext ( 'database' );
$collection = $queueForEvents -> getContext ( 'collection' );
$bucket = $queueForEvents -> getContext ( 'bucket' );
2022-07-22 18:00:42 +12:00
$target = Realtime :: fromPayload (
2022-08-11 19:02:40 +12:00
// Pass first, most verbose event pattern
2022-07-22 18:00:42 +12:00
event : $allEvents [ 0 ],
payload : $payload ,
project : $project ,
database : $db ,
collection : $collection ,
bucket : $bucket ,
);
Realtime :: send (
projectId : $target [ 'projectId' ] ? ? $project -> getId (),
2022-12-21 05:11:30 +13:00
payload : $queueForEvents -> getPayload (),
2022-07-22 18:00:42 +12:00
events : $allEvents ,
channels : $target [ 'channels' ],
roles : $target [ 'roles' ],
options : [
'permissionsChanged' => $target [ 'permissionsChanged' ],
2022-12-21 05:11:30 +13:00
'userId' => $queueForEvents -> getParam ( 'userId' )
2022-07-22 18:00:42 +12:00
]
);
2021-03-01 07:36:13 +13:00
}
2022-04-19 04:21:45 +12:00
}
2021-03-01 07:36:13 +13:00
2023-02-20 00:04:12 +13:00
$route = $utopia -> getRoute ();
2022-08-15 07:27:43 +12:00
$requestParams = $route -> getParamsValues ();
2022-08-09 00:19:41 +12:00
2022-08-18 02:29:22 +12:00
/**
* Audit labels
*/
2022-08-17 00:28:30 +12:00
$pattern = $route -> getLabel ( 'audits.resource' , null );
if ( ! empty ( $pattern )) {
$resource = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
if ( ! empty ( $resource ) && $resource !== $pattern ) {
2022-12-21 05:11:30 +13:00
$queueForAudits -> setResource ( $resource );
2022-08-08 02:30:47 +12:00
}
2022-08-08 03:49:30 +12:00
}
2022-08-13 20:02:00 +12:00
2023-07-07 12:12:39 +12:00
if ( ! $user -> isEmpty ()) {
2023-10-02 06:39:26 +13:00
$queueForAudits -> setUser ( $user );
2021-03-12 05:28:03 +13:00
}
2021-10-08 04:35:17 +13:00
2022-12-21 05:11:30 +13:00
if ( ! empty ( $queueForAudits -> getResource ()) && ! empty ( $queueForAudits -> getUser () -> getId ())) {
2022-08-17 00:28:30 +12:00
/**
* audits . payload is switched to default true
* in order to auto audit payload for all endpoints
*/
$pattern = $route -> getLabel ( 'audits.payload' , true );
if ( ! empty ( $pattern )) {
2022-12-21 05:11:30 +13:00
$queueForAudits -> setPayload ( $responsePayload );
2022-08-17 00:28:30 +12:00
}
2022-12-21 05:11:30 +13:00
foreach ( $queueForEvents -> getParams () as $key => $value ) {
$queueForAudits -> setParam ( $key , $value );
2021-03-01 07:36:13 +13:00
}
2022-12-21 05:11:30 +13:00
$queueForAudits -> trigger ();
2022-04-19 04:21:45 +12:00
}
2021-10-08 04:35:17 +13:00
2022-12-21 05:11:30 +13:00
if ( ! empty ( $queueForDeletes -> getType ())) {
$queueForDeletes -> trigger ();
2022-04-14 00:39:31 +12:00
}
2021-10-08 04:35:17 +13:00
2022-12-21 05:11:30 +13:00
if ( ! empty ( $queueForDatabase -> getType ())) {
$queueForDatabase -> trigger ();
2022-07-22 18:00:42 +12:00
}
2021-10-08 04:35:17 +13:00
2022-08-18 02:29:22 +12:00
/**
* Cache label
*/
2022-08-17 03:02:17 +12:00
$useCache = $route -> getLabel ( 'cache' , false );
if ( $useCache ) {
2022-11-10 23:08:01 +13:00
$resource = $resourceType = null ;
2022-08-10 01:43:37 +12:00
$data = $response -> getPayload ();
2022-08-31 00:44:37 +12:00
2022-08-17 03:02:17 +12:00
if ( ! empty ( $data [ 'payload' ])) {
$pattern = $route -> getLabel ( 'cache.resource' , null );
if ( ! empty ( $pattern )) {
$resource = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
}
2022-08-15 21:05:41 +12:00
2022-11-10 23:08:01 +13:00
$pattern = $route -> getLabel ( 'cache.resourceType' , null );
if ( ! empty ( $pattern )) {
$resourceType = $parseLabel ( $pattern , $responsePayload , $requestParams , $user );
}
2023-09-06 03:29:11 +12:00
$key = md5 ( $request -> getURI () . implode ( '*' , $request -> getParams ()) . '*' . APP_CACHE_BUSTER );
2022-08-17 03:02:17 +12:00
$data = json_encode ([
2023-05-24 01:43:03 +12:00
'resourceType' => $resourceType ,
'resource' => $resource ,
'contentType' => $response -> getContentType (),
'payload' => base64_encode ( $data [ 'payload' ]),
2022-08-17 03:02:17 +12:00
]) ;
2022-08-15 21:05:41 +12:00
2022-08-17 03:02:17 +12:00
$signature = md5 ( $data );
2023-06-03 02:56:09 +12:00
$cacheLog = Authorization :: skip ( fn () => $dbForProject -> getDocument ( 'cache' , $key ));
2022-08-31 20:52:55 +12:00
$accessedAt = $cacheLog -> getAttribute ( 'accessedAt' , '' );
2022-09-01 01:07:27 +12:00
$now = DateTime :: now ();
2022-08-17 03:02:17 +12:00
if ( $cacheLog -> isEmpty ()) {
Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'cache' , new Document ([
2022-08-10 01:43:37 +12:00
'$id' => $key ,
2022-08-15 21:05:41 +12:00
'resource' => $resource ,
2022-09-01 01:07:27 +12:00
'accessedAt' => $now ,
2022-08-15 21:05:41 +12:00
'signature' => $signature ,
2022-08-10 01:43:37 +12:00
])));
2022-09-01 01:11:23 +12:00
} elseif ( DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), - APP_CACHE_UPDATE )) > $accessedAt ) {
2022-09-01 01:07:27 +12:00
$cacheLog -> setAttribute ( 'accessedAt' , $now );
2022-08-10 01:43:37 +12:00
Authorization :: skip ( fn () => $dbForProject -> updateDocument ( 'cache' , $cacheLog -> getId (), $cacheLog ));
2022-08-17 03:02:17 +12:00
}
2022-08-15 03:01:34 +12:00
2022-08-17 03:02:17 +12:00
if ( $signature !== $cacheLog -> getAttribute ( 'signature' )) {
$cache = new Cache (
new Filesystem ( APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project -> getId ())
);
$cache -> save ( $key , $data );
}
2022-08-16 00:16:32 +12:00
}
2022-07-24 05:42:42 +12:00
}
2023-10-25 20:39:59 +13:00
if ( $project -> getId () !== 'console' ) {
if ( $mode !== APP_MODE_ADMIN ) {
$fileSize = 0 ;
$file = $request -> getFiles ( 'file' );
if ( ! empty ( $file )) {
$fileSize = ( \is_array ( $file [ 'size' ]) && isset ( $file [ 'size' ][ 0 ])) ? $file [ 'size' ][ 0 ] : $file [ 'size' ];
2022-08-10 14:33:46 +12:00
}
2023-10-25 20:39:59 +13:00
$queueForUsage
-> addMetric ( METRIC_NETWORK_REQUESTS , 1 )
-> addMetric ( METRIC_NETWORK_INBOUND , $request -> getSize () + $fileSize )
-> addMetric ( METRIC_NETWORK_OUTBOUND , $response -> getSize ());
2022-08-17 22:55:01 +12:00
}
2023-10-25 20:39:59 +13:00
$queueForUsage
-> setProject ( $project )
-> trigger ();
2022-07-22 18:00:42 +12:00
}
2023-12-16 10:37:26 +13:00
/**
* Update user last activity
*/
if ( ! $user -> isEmpty ()) {
$accessedAt = $user -> getAttribute ( 'accessedAt' , '' );
if ( DateTime :: formatTz ( DateTime :: addSeconds ( new \DateTime (), - APP_USER_ACCCESS )) > $accessedAt ) {
$user -> setAttribute ( 'accessedAt' , DateTime :: now ());
if ( APP_MODE_ADMIN !== $mode ) {
$dbForProject -> updateDocument ( 'users' , $user -> getId (), $user );
} else {
$dbForConsole -> updateDocument ( 'users' , $user -> getId (), $user );
}
}
}
2022-07-22 18:00:42 +12:00
});
2023-11-07 10:28:45 +13:00
App :: init ()
-> groups ([ 'usage' ])
-> action ( function () {
if ( App :: getEnv ( '_APP_USAGE_STATS' , 'enabled' ) !== 'enabled' ) {
throw new Exception ( Exception :: GENERAL_USAGE_DISABLED );
}
});