diff --git a/app/config/collections.php b/app/config/collections.php index f2ee405c92..0d42dfe895 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1416,17 +1416,6 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], - [ - '$id' => ID::custom('internal'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => false, - 'default' => false, - 'array' => false, - ], [ '$id' => ID::custom('enabled'), 'type' => Database::VAR_BOOLEAN, @@ -1495,16 +1484,9 @@ $commonCollections = [ 'orders' => [Database::ORDER_ASC], ], [ - '$id' => ID::custom('_key_internal'), + '$id' => ID::custom('_key_enabled_type'), 'type' => Database::INDEX_KEY, - 'attributes' => ['internal'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_internal_type'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['internal','type'], + 'attributes' => ['enabled','type'], 'lengths' => [], 'orders' => [Database::ORDER_ASC], ], diff --git a/app/config/errors.php b/app/config/errors.php index e915091940..1117f20e48 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -781,11 +781,6 @@ return [ 'description' => 'Provider with the requested ID is of incorrect type: ', 'code' => 400, ], - Exception::PROVIDER_INTERNAL_UPDATE_DISABLED => [ - 'name' => Exception::PROVIDER_INTERNAL_UPDATE_DISABLED, - 'description' => 'Provider with the requested ID cannot be disabled.', - 'code' => 400, - ], /** Topic Errors */ Exception::TOPIC_NOT_FOUND => [ diff --git a/app/config/variables.php b/app/config/variables.php index 9d555bf013..6f49d4a5da 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -440,7 +440,7 @@ return [ 'variables' => [ [ 'name' => '_APP_SMS_PROVIDER', - 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.", + 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, textmagic, telesign, msg91, and vonage.", 'introduction' => '0.15.0', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 1d6780f48b..6486147a84 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1260,12 +1260,7 @@ App::post('/v1/account/sessions/phone') ->inject('queueForMessaging') ->inject('locale') ->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -1351,35 +1346,18 @@ App::post('/v1/account/sessions/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->findOne('targets', [ - Query::equal('identifier', [$phone]), - ]); - if (!$target || $target->isEmpty()) { - $target = $dbForProject->createDocument('targets', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $phone, - ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); - } - - $messageDoc = $dbForProject->createDocument('messages', new Document([ + $messageDoc = new Document([ '$id' => $token->getId(), - 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$phone]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); @@ -2964,12 +2942,7 @@ App::post('/v1/account/verification/phone') ->inject('project') ->inject('locale') ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -3015,35 +2988,17 @@ App::post('/v1/account/verification/phone') $message = $message->setParam('{{token}}', $secret); $message = $message->render(); - $target = $dbForProject->findOne('targets', [ - Query::equal('identifier', [$user->getAttribute('phone')]), - ]); - - if (!$target || $target->isEmpty()) { - $target = $dbForProject->createDocument('targets', new Document([ - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - 'userId' => $user->getId(), - 'userInternalId' => $user->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $user->getAttribute('phone'), - ])); - $dbForProject->deleteCachedDocument('users', $user->getId()); - } - - $messageDoc = $dbForProject->createDocument('messages', new Document([ + $messageDoc = new Document([ '$id' => $verification->getId(), - 'targets' => [$target->getId()], 'data' => [ 'content' => $message, ], - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$user->getAttribute('phone')]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 1f4f7b62b6..e1e531c1a1 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -79,16 +79,6 @@ App::post('/v1/messaging/providers/mailgun') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['email']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -141,16 +131,6 @@ App::post('/v1/messaging/providers/sendgrid') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -205,16 +185,6 @@ App::post('/v1/messaging/providers/msg91') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -269,16 +239,6 @@ App::post('/v1/messaging/providers/telesign') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -333,16 +293,6 @@ App::post('/v1/messaging/providers/textmagic') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -397,16 +347,6 @@ App::post('/v1/messaging/providers/twilio') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -461,16 +401,6 @@ App::post('/v1/messaging/providers/vonage') ] ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -519,16 +449,6 @@ App::post('/v1/messaging/providers/fcm') ], ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['push']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -585,16 +505,6 @@ App::post('/v1/messaging/providers/apns') ], ]); - // Check if a internal provider exists, if not, set this one as internal - if ( - empty($dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['push']) - ])) - ) { - $provider->setAttribute('internal', true); - } - try { $provider = $dbForProject->createDocument('providers', $provider); } catch (DuplicateException) { @@ -776,7 +686,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->param('apiKey', '', new Text(0), 'Mailgun API Key.', true) @@ -784,7 +693,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $isEuRegion, string $from, string $apiKey, string $domain, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -806,14 +715,7 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -835,15 +737,6 @@ App::patch('/v1/messaging/providers/mailgun/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -868,13 +761,12 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Sendgrid API key.', true) ->param('from', '', new Text(256), 'Sender email address.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -896,14 +788,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -915,15 +800,6 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -948,14 +824,13 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('senderId', '', new Text(0), 'Msg91 Sender ID.', true) ->param('authKey', '', new Text(0), 'Msg91 Auth Key.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $senderId, string $authKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $senderId, string $authKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -977,14 +852,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1002,15 +870,6 @@ App::patch('/v1/messaging/providers/msg91/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1035,14 +894,13 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Telesign username.', true) ->param('password', '', new Text(0), 'Telesign password.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $password, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $password, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1064,14 +922,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1089,15 +940,6 @@ App::patch('/v1/messaging/providers/telesign/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1122,14 +964,13 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('username', '', new Text(0), 'Textmagic username.', true) ->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $username, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1151,14 +992,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1176,15 +1010,6 @@ App::patch('/v1/messaging/providers/textmagic/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1209,14 +1034,13 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('accountSid', null, new Text(0), 'Twilio account secret ID.', true) ->param('authToken', null, new Text(0), 'Twilio authentication token.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $accountSid, string $authToken, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $accountSid, string $authToken, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1238,14 +1062,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1263,15 +1080,6 @@ App::patch('/v1/messaging/providers/twilio/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1296,14 +1104,13 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('apiKey', '', new Text(0), 'Vonage API key.', true) ->param('apiSecret', '', new Text(0), 'Vonage API secret.', true) ->param('from', '', new Text(256), 'Sender number.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $apiKey, string $apiSecret, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $apiKey, string $apiSecret, string $from, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1325,14 +1132,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId') ]); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1350,15 +1150,6 @@ App::patch('/v1/messaging/providers/vonage/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1383,12 +1174,11 @@ App::patch('/v1/messaging/providers/fcm/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('serverKey', '', new Text(0), 'FCM Server Key.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $serverKey, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1404,14 +1194,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider->setAttribute('name', $name); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1421,15 +1204,6 @@ App::patch('/v1/messaging/providers/fcm/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); @@ -1455,7 +1229,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->param('providerId', '', new UID(), 'Provider ID.') ->param('name', '', new Text(128), 'Provider name.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) - ->param('internal', null, new Boolean(), 'Set as internal. Internal providers are used in services other than Messaging service such as Authentication service', true) ->param('authKey', '', new Text(0), 'APNS authentication key.', true) ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) ->param('teamId', '', new Text(0), 'APNS team ID.', true) @@ -1464,7 +1237,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, ?bool $internal, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, string $endpoint, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1480,14 +1253,7 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('name', $name); } - if ($internal === true) { - $provider->setAttribute('internal', $internal); - } - if ($enabled === true || $enabled === false) { - if ($provider->getAttribute('internal') === true && $enabled === false) { - throw new Exception(Exception::PROVIDER_INTERNAL_UPDATE_DISABLED); - } $provider->setAttribute('enabled', $enabled); } @@ -1517,15 +1283,6 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider = $dbForProject->updateDocument('providers', $provider->getId(), $provider); - if ($internal === true) { - $internalProvider = $dbForProject->findOne('providers', [ - 'internal' => true, - 'type' => 'email', - ]); - $internalProvider->setAttribute('internal', false); - $dbForProject->updateDocument('providers', $internalProvider->getId(), $internalProvider); - } - $queueForEvents ->setParam('providerId', $provider->getId()); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index b19080e894..2ba27efcb3 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -628,12 +628,7 @@ App::post('/v1/teams/:teamId/memberships') ->trigger() ; } elseif (!empty($phone)) { - $provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), - Query::equal('type', ['sms']) - ])); - - if ($provider === false || $provider->isEmpty()) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); } @@ -647,27 +642,17 @@ App::post('/v1/teams/:teamId/memberships') $message = $message->setParam('{{token}}', $url); $message = $message->render(); - $target = $dbForProject->createDocument('targets', new Document([ - 'userId' => $invitee->getId(), - 'userInternalId' => $invitee->getInternalId(), - 'providerType' => 'sms', - 'identifier' => $phone, - ])); - - $messageDoc = $dbForProject->createDocument('messages', new Document([ - // Here membership ID is used as message ID so that it can be used in test cases to verify the message - '$id' => $membership->getId(), - 'targets' => [$target->getId()], + $messageDoc = new Document([ + '$id' => ID::unique(), 'data' => [ 'content' => $message, ], - 'providerId' => $provider->getId(), - 'providerInternalId' => $provider->getInternalId(), - 'deliveryTime' => Datetime::now(), - ])); + ]); $queueForMessaging - ->setMessageId($messageDoc->getId()) + ->setMessage($messageDoc) + ->setRecipients([$phone]) + ->setProviderType('SMS') ->setProject($project) ->trigger(); } diff --git a/docker-compose.yml b/docker-compose.yml index 8255508356..a570c5b619 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -582,6 +582,8 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_SMS_FROM + - _APP_SMS_PROVIDER appwrite-worker-migrations: entrypoint: worker-migrations diff --git a/src/Appwrite/Event/Messaging.php b/src/Appwrite/Event/Messaging.php index 76abecd1c0..62d41f8c3b 100644 --- a/src/Appwrite/Event/Messaging.php +++ b/src/Appwrite/Event/Messaging.php @@ -9,7 +9,11 @@ use Utopia\Queue\Client; class Messaging extends Event { protected ?string $messageId = null; - private ?string $deliveryTime = null; + protected ?Document $message = null; + protected ?array $recipients = null; + protected ?string $deliveryTime = null; + protected ?string $providerType = null; + public function __construct(protected Connection $connection) { @@ -20,6 +24,52 @@ class Messaging extends Event ->setClass(Event::MESSAGING_CLASS_NAME); } + /** + * Sets recipient for the messaging event. + * + * @param string[] $recipients + * @return self + */ + public function setRecipients(array $recipients): self + { + $this->recipients = $recipients; + + return $this; + } + + /** + * Returns set recipient for messaging event. + * + * @return string[] + */ + public function getRecipient(): array + { + return $this->recipients; + } + + /** + * Sets message document for the messaging event. + * + * @param Document $message + * @return self + */ + public function setMessage(Document $message): self + { + $this->message = $message; + + return $this; + } + + /** + * Returns message document for the messaging event. + * + * @return string + */ + public function getMessage(): Document + { + return $this->message; + } + /** * Sets message ID for the messaging event. * @@ -43,6 +93,29 @@ class Messaging extends Event return $this->messageId; } + /** + * Sets provider type for the messaging event. + * + * @param string $providerType + * @return self + */ + public function setProviderType(string $providerType): self + { + $this->providerType = $providerType; + + return $this; + } + + /** + * Returns set provider type for the messaging event. + * + * @return string + */ + public function getProviderType(): string + { + return $this->providerType; + } + /** * Sets Delivery time for the messaging event. * @@ -92,6 +165,9 @@ class Messaging extends Event 'project' => $this->project, 'user' => $this->user, 'messageId' => $this->messageId, + 'message' => $this->message, + 'recipients' => $this->recipients, + 'providerType' => $this->providerType, ]); } } diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 68bae1b4d0..299ef4526f 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -3,7 +3,10 @@ namespace Appwrite\Platform\Workers; use Appwrite\Extend\Exception; +use Utopia\App; use Utopia\CLI\Console; +use Utopia\Database\Helpers\ID; +use Utopia\DSN\DSN; use Utopia\Platform\Action; use Utopia\Queue\Message; use Utopia\Database\Database; @@ -63,9 +66,15 @@ class Messaging extends Action return; } - $message = $dbForProject->getDocument('messages', $payload['messageId']); + if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) { + if ($payload['providerType'] === 'SMS') { + $this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']); + } + } else { + $message = $dbForProject->getDocument('messages', $payload['messageId']); - $this->processMessage($dbForProject, $message); + $this->processMessage($dbForProject, $message); + } } private function processMessage(Database $dbForProject, Document $message): void @@ -99,7 +108,7 @@ class Messaging extends Action } $internalProvider = $dbForProject->findOne('providers', [ - Query::equal('internal', [true]), + Query::equal('enabled', [true]), Query::equal('type', [$recipients[0]->getAttribute('providerType')]), ]); @@ -215,6 +224,75 @@ class Messaging extends Action $dbForProject->updateDocument('messages', $message->getId(), $message); } + private function processInternalSMSMessage(Document $message, array $recipients): void + { + if (empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) { + Console::info('Skipped SMS processing. No Phone configuration has been set.'); + return; + } + + $smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER')); + $host = $smsDSN->getHost(); + $password = $smsDSN->getPassword(); + $user = $smsDSN->getUser(); + + $from = App::getEnv('_APP_SMS_FROM'); + + $provider = new Document([ + '$id' => ID::unique(), + 'provider' => $host, + 'type' => 'sms', + 'name' => 'Internal SMS', + 'enabled' => true, + 'credentials' => match ($host) { + 'twilio' => [ + 'accountSid' => $user, + 'authToken' => $password + ], + 'textmagic' => [ + 'username' => $user, + 'apiKey' => $password + ], + 'telesign' => [ + 'username' => $user, + 'password' => $password + ], + 'msg91' => [ + 'senderId' => $user, + 'authKey' => $password + ], + 'vonage' => [ + 'apiKey' => $user, + 'apiSecret' => $password + ], + default => null + }, + 'options' => [ + 'from' => $from + ] + ]); + + $adapter = $this->sms($provider); + + $maxBatchSize = $adapter->getMaxMessagesPerRequest(); + $batches = \array_chunk($recipients, $maxBatchSize); + $batchIndex = 0; + + batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) { + return function () use ($batch, $message, $provider, $adapter, $batchIndex) { + $message->setAttribute('to', $batch); + + $data = $this->buildSMSMessage($message, $provider); + + try { + $adapter->send($data); + } catch (\Exception $e) { + Console::error('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage()); + } + }; + }, $batches)); + } + public function shutdown(): void { } @@ -225,7 +303,7 @@ class Messaging extends Action return match ($provider->getAttribute('provider')) { 'mock' => new Mock('username', 'password'), 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']), - 'text-magic' => new TextMagic($credentials['username'], $credentials['apiKey']), + 'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']), 'telesign' => new Telesign($credentials['username'], $credentials['password']), 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']), 'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']), diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php index 7760acbdad..a28f5e8437 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Providers.php @@ -8,7 +8,6 @@ class Providers extends Base 'name', 'provider', 'type', - 'internal', 'enabled', ]; diff --git a/src/Appwrite/Utopia/Response/Model/Provider.php b/src/Appwrite/Utopia/Response/Model/Provider.php index a121753275..f8a0514020 100644 --- a/src/Appwrite/Utopia/Response/Model/Provider.php +++ b/src/Appwrite/Utopia/Response/Model/Provider.php @@ -40,12 +40,6 @@ class Provider extends Model 'default' => '', 'example' => 'mailgun', ]) - ->addRule('internal', [ - 'type' => self::TYPE_BOOLEAN, - 'description' => 'Is this a pre-configured provider instance?', - 'default' => false, - 'example' => true, - ]) ->addRule('enabled', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is provider enabled?', diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index d56f93a42c..1cc9e6c325 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -744,33 +744,8 @@ class AccountCustomClientTest extends Scope public function testCreatePhone(): array { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } + $number = '+123456789'; - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { - $this->markTestSkipped('SMS provider not configured'); - } - - $number = $to; - $response = $this->client->call(Client::METHOD_POST, '/messaging/providers/msg91', \array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]), [ - 'providerId' => ID::unique(), - 'name' => 'Sms provider', - 'senderId' => $senderId, - 'authKey' => $authKey, - 'from' => $from, - ]); - $this->assertEquals(201, $response['headers']['status-code']); /** * Test for SUCCESS */ @@ -781,7 +756,6 @@ class AccountCustomClientTest extends Scope ]), [ 'userId' => ID::unique(), 'phone' => $number, - 'from' => $from, ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -807,19 +781,17 @@ class AccountCustomClientTest extends Scope \sleep(5); - $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + $smsRequest = $this->getLastRequest(); - $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTotal']); - $this->assertEquals(0, \count($message['body']['deliveryErrors'])); + $this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']); + $this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']); + $this->assertEquals('username', $smsRequest['headers']['X-Username']); + $this->assertEquals('password', $smsRequest['headers']['X-Key']); + $this->assertEquals('POST', $smsRequest['method']); + $this->assertEquals('+123456789', $smsRequest['data']['from']); + $this->assertEquals($number, $smsRequest['data']['to']); - - $data['token'] = $message['body']['data']['content']; + $data['token'] = $smsRequest['data']['message']; $data['id'] = $userId; $data['number'] = $number; @@ -1018,8 +990,6 @@ class AccountCustomClientTest extends Scope public function testPhoneVerification(array $data): array { $session = $data['session'] ?? ''; - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $from = $smsDSN->getParam('from'); /** * Test for SUCCESS @@ -1030,28 +1000,19 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, - ]), ['from' => $from]); + ])); $this->assertEquals(201, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); $this->assertEmpty($response['body']['secret']); $this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire'])); - \sleep(3); + \sleep(2); - $message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $response['body']['$id'], [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - $this->assertEquals(200, $message['headers']['status-code']); - $this->assertEquals(1, $message['body']['deliveredTotal']); - $this->assertEquals(0, \count($message['body']['deliveryErrors'])); + $smsRequest = $this->getLastRequest(); return \array_merge($data, [ - 'token' => $message['body']['data']['content'] + 'token' => $smsRequest['data']['secret'] ]); } diff --git a/tests/e2e/Services/GraphQL/AccountTest.php b/tests/e2e/Services/GraphQL/AccountTest.php index 9d1da09feb..3e2cfac29c 100644 --- a/tests/e2e/Services/GraphQL/AccountTest.php +++ b/tests/e2e/Services/GraphQL/AccountTest.php @@ -124,38 +124,7 @@ class AccountTest extends Scope */ public function testCreatePhoneVerification(): array { - if (empty(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) { - $this->markTestSkipped('SMS DSN not provided'); - } - - $smsDSN = new DSN(App::getEnv('_APP_MESSAGE_SMS_TEST_DSN')); - $to = $smsDSN->getParam('to'); - $from = $smsDSN->getParam('from'); - $authKey = $smsDSN->getPassword(); - $senderId = $smsDSN->getUser(); - - if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) { - $this->markTestSkipped('SMS provider not configured'); - } - $projectId = $this->getProject()['$id']; - $query = $this->getQuery(self::$CREATE_MSG91_PROVIDER); - $graphQLPayload = [ - 'query' => $query, - 'variables' => [ - 'providerId' => ID::unique(), - 'name' => 'Sms Provider', - 'from' => $from, - 'senderId' => $senderId, - 'authKey' => $authKey, - ], - ]; - - $response = $this->client->call(Client::METHOD_POST, '/graphql', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $graphQLPayload); $query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION); $graphQLPayload = [ diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index b57b680674..707905892c 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -1796,7 +1796,6 @@ trait Base name provider type - internal enabled } }'; @@ -1807,7 +1806,6 @@ trait Base name provider type - internal enabled } }'; @@ -1818,7 +1816,6 @@ trait Base name provider type - internal enabled } }'; @@ -1829,7 +1826,6 @@ trait Base name provider type - internal enabled } }'; @@ -1840,7 +1836,6 @@ trait Base name provider type - internal enabled } }'; @@ -1851,7 +1846,6 @@ trait Base name provider type - internal enabled } }'; @@ -1862,7 +1856,6 @@ trait Base name provider type - internal enabled } }'; @@ -1873,7 +1866,6 @@ trait Base name provider type - internal enabled } }'; @@ -1884,7 +1876,6 @@ trait Base name provider type - internal enabled } }'; @@ -1897,7 +1888,7 @@ trait Base name provider type - internal + enabled } } @@ -1909,7 +1900,6 @@ trait Base name provider type - internal enabled } }'; @@ -1920,7 +1910,6 @@ trait Base name provider type - internal enabled } }'; @@ -1931,7 +1920,6 @@ trait Base name provider type - internal enabled } }'; @@ -1942,7 +1930,6 @@ trait Base name provider type - internal enabled } }'; @@ -1953,7 +1940,6 @@ trait Base name provider type - internal enabled } }'; @@ -1964,7 +1950,6 @@ trait Base name provider type - internal enabled } }'; @@ -1975,7 +1960,6 @@ trait Base name provider type - internal enabled } }'; @@ -1986,7 +1970,6 @@ trait Base name provider type - internal enabled } }'; @@ -1997,7 +1980,6 @@ trait Base name provider type - internal enabled } }'; @@ -2008,7 +1990,6 @@ trait Base name provider type - internal enabled } }';