From ef47618bf3dc7be6f9bb60881f051a3df9abd028 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 21 Aug 2025 12:20:50 +0530 Subject: [PATCH] update: user create events logic. --- app/controllers/api/account.php | 6 +- app/controllers/api/users.php | 65 ++++++++++++++----- app/controllers/shared/api.php | 52 +-------------- .../Utopia/Response/Model/BaseList.php | 3 +- 4 files changed, 55 insertions(+), 71 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4956e8b6a2..1eeb10b459 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -292,6 +292,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res App::post('/v1/account') ->desc('Create account') ->groups(['api', 'account', 'auth']) + ->label('event', 'users.[userId].create') ->label('scope', 'sessions.write') ->label('auth.type', 'email-password') ->label('audits.event', 'user.create') @@ -322,7 +323,8 @@ App::post('/v1/account') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $email = \strtolower($email); if ('console' === $project->getId()) { @@ -432,6 +434,8 @@ App::post('/v1/account') Authorization::setRole(Role::user($user->getId())->toString()); Authorization::setRole(Role::users()->toString()); + $queueForEvents->setParam('userId', $user->getId()); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 993f8a03c9..7918cb43d8 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -59,8 +59,19 @@ use Utopia\Validator\Text; use Utopia\Validator\WhiteList; /** TODO: Remove function when we move to using utopia/platform */ -function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document -{ +function createUser( + string $hash, + mixed $hashOptions, + string $userId, + ?string $email, + ?string $password, + ?string $phone, + string $name, + Document $project, + Database $dbForProject, + Event $queueForEvents, + Hooks $hooks, +): Document { $plaintextPassword = $password; $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array $passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0; @@ -184,12 +195,15 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e throw new Exception(Exception::USER_ALREADY_EXISTS); } + $queueForEvents->setParam('userId', $user->getId()); + return $user; } App::post('/v1/users') ->desc('Create user') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -215,8 +229,9 @@ App::post('/v1/users') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_USER); @@ -225,6 +240,7 @@ App::post('/v1/users') App::post('/v1/users/bcrypt') ->desc('Create user with bcrypt password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -249,8 +265,9 @@ App::post('/v1/users/bcrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -260,6 +277,7 @@ App::post('/v1/users/bcrypt') App::post('/v1/users/md5') ->desc('Create user with MD5 password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -284,8 +302,9 @@ App::post('/v1/users/md5') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -295,6 +314,7 @@ App::post('/v1/users/md5') App::post('/v1/users/argon2') ->desc('Create user with Argon2 password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -319,8 +339,9 @@ App::post('/v1/users/argon2') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -330,6 +351,7 @@ App::post('/v1/users/argon2') App::post('/v1/users/sha') ->desc('Create user with SHA password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -355,14 +377,15 @@ App::post('/v1/users/sha') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $options = '{}'; if (!empty($passwordVersion)) { $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -372,6 +395,7 @@ App::post('/v1/users/sha') App::post('/v1/users/phpass') ->desc('Create user with PHPass password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -396,8 +420,9 @@ App::post('/v1/users/phpass') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -407,6 +432,7 @@ App::post('/v1/users/phpass') App::post('/v1/users/scrypt') ->desc('Create user with Scrypt password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -436,7 +462,8 @@ App::post('/v1/users/scrypt') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { $options = [ 'salt' => $passwordSalt, 'costCpu' => $passwordCpu, @@ -445,7 +472,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -455,6 +482,7 @@ App::post('/v1/users/scrypt') App::post('/v1/users/scrypt-modified') ->desc('Create user with Scrypt modified password') ->groups(['api', 'users']) + ->label('event', 'users.[userId].create') ->label('scope', 'users.write') ->label('audits.event', 'user.create') ->label('audits.resource', 'user/{response.$id}') @@ -482,8 +510,9 @@ App::post('/v1/users/scrypt-modified') ->inject('project') ->inject('dbForProject') ->inject('hooks') - ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks); + ->inject('queueForEvents') + ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks, Event $queueForEvents) { + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks); $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index c2f5c415a0..5cbaac4f34 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -60,38 +60,6 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar return $label; }; -$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) { - // Only trigger events for user creation with the database listener. - if ($document->getCollection() !== 'users') { - return; - } - - $queueForEvents - ->setEvent('users.[userId].create') - ->setParam('userId', $document->getId()) - ->setPayload($response->output($document, Response::MODEL_USER)); - - // Trigger functions, webhooks, and realtime events - $queueForFunctions - ->from($queueForEvents) - ->trigger(); - - - /** Trigger webhooks events only if a project has them enabled */ - if (!empty($project->getAttribute('webhooks'))) { - $queueForWebhooks - ->from($queueForEvents) - ->trigger(); - } - - /** Trigger realtime events only for non console events */ - if ($queueForEvents->getProject()->getId() !== 'console') { - $queueForRealtime - ->from($queueForEvents) - ->trigger(); - } -}; - $usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) { $value = 1; @@ -423,7 +391,7 @@ App::init() ->inject('plan') ->inject('devKey') ->inject('telemetry') - ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) { + ->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener) { $route = $utopia->getRoute(); @@ -532,28 +500,12 @@ App::init() $queueForBuilds->setProject($project); $queueForMessaging->setProject($project); - // Clone the queues, to prevent events triggered by the database listener - // from overwriting the events that are supposed to be triggered in the shutdown hook. - $queueForEventsClone = new Event($publisher); - $queueForFunctions = new Func($publisher); - $queueForWebhooks = new Webhook($publisher); - $queueForRealtime = new Realtime(); - $dbForProject ->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) ->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) - ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)) - ->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener( - $project, - $document, - $response, - $queueForEventsClone->from($queueForEvents), - $queueForFunctions->from($queueForEvents), - $queueForWebhooks->from($queueForEvents), - $queueForRealtime->from($queueForEvents) - )); + ->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage)); $useCache = $route->getLabel('cache', false); $storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load'); diff --git a/src/Appwrite/Utopia/Response/Model/BaseList.php b/src/Appwrite/Utopia/Response/Model/BaseList.php index f1da542c6c..447a4436e3 100644 --- a/src/Appwrite/Utopia/Response/Model/BaseList.php +++ b/src/Appwrite/Utopia/Response/Model/BaseList.php @@ -31,8 +31,7 @@ class BaseList extends Model string $model, bool $paging = true, bool $public = true - ) - { + ) { $this->name = $name; $this->type = $type; $this->public = $public;