diff --git a/.env b/.env index 8a7a53e6f4..31474bf0b6 100644 --- a/.env +++ b/.env @@ -58,6 +58,7 @@ _APP_SMTP_USERNAME= _APP_SMTP_PASSWORD= _APP_SMS_PROVIDER=sms://username:password@mock _APP_SMS_FROM=+123456789 +_APP_SMS_PROJECTS_DENY_LIST= _APP_STORAGE_LIMIT=30000000 _APP_STORAGE_PREVIEW_LIMIT=20000000 _APP_FUNCTIONS_SIZE_LIMIT=30000000 diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 99df2f459e..2209459d3e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -889,7 +889,7 @@ App::delete('/v1/account/identities/:identityId') App::post('/v1/account/sessions/magic-url') ->desc('Create magic URL session') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'auth']) ->label('scope', 'public') ->label('auth.type', 'magic-url') ->label('audits.event', 'session.create') @@ -902,8 +902,8 @@ App::post('/v1/account/sessions/magic-url') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},email:{param-email}') + ->label('abuse-limit', 60) + ->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}', 'url:{url},projectId:{projectId}']) ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('email', '', new Email(), 'User email.') ->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) @@ -1223,7 +1223,7 @@ App::put('/v1/account/sessions/magic-url') App::post('/v1/account/sessions/phone') ->desc('Create phone session') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'auth']) ->label('scope', 'public') ->label('auth.type', 'phone') ->label('audits.event', 'session.create') @@ -1237,7 +1237,7 @@ App::post('/v1/account/sessions/phone') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},phone:{param-phone}') + ->label('abuse-key', ['url:{url},phone:{param-phone}', 'url:{url},ip:{ip}', 'url:{url},projectId:{projectId}']) ->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->inject('request') @@ -2391,7 +2391,7 @@ App::post('/v1/account/recovery') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) ->label('abuse-limit', 10) - ->label('abuse-key', ['url:{url},email:{param-email}', 'ip:{ip}']) + ->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}']) ->param('email', '', new Email(), 'User email.') ->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) ->inject('request') @@ -2864,8 +2864,9 @@ App::put('/v1/account/verification') App::post('/v1/account/verification/phone') ->desc('Create phone verification') - ->groups(['api', 'account']) + ->groups(['api', 'account', 'auth']) ->label('scope', 'account') + ->label('auth.type', 'phone') ->label('event', 'users.[userId].verification.[tokenId].create') ->label('audits.event', 'verification.create') ->label('audits.resource', 'user/{response.userId}') @@ -2877,7 +2878,7 @@ App::post('/v1/account/verification/phone') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) ->label('abuse-limit', 10) - ->label('abuse-key', 'userId:{userId}') + ->label('abuse-key', ['url:{url},userId:{userId}', 'url:{url},ip:{ip}', 'url:{url},projectId:{projectId}']) ->inject('request') ->inject('response') ->inject('user') diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index d6b5a2c680..df6ec002cb 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -177,6 +177,7 @@ App::init() $end = $request->getContentRangeEnd(); $timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject); $timeLimit + ->setParam('{projectId}', $project->getId()) ->setParam('{userId}', $user->getId()) ->setParam('{userAgent}', $request->getUserAgent('')) ->setParam('{ip}', $request->getIP()) @@ -335,7 +336,7 @@ App::init() break; case 'magic-url': - if ($project->getAttribute('usersAuthMagicURL', true) === false) { + if (($auths['usersAuthMagicURL'] ?? true) === false) { throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project'); } break; @@ -346,6 +347,12 @@ App::init() } break; + case 'phone': + if (($auths['phone'] ?? true) === false) { + throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project'); + } + break; + case 'invites': if (($auths['invites'] ?? true) === false) { throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project'); @@ -359,7 +366,7 @@ App::init() break; default: - throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route'); + throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication type: ' . $route->getLabel('auth.type', '')); break; } }); diff --git a/app/controllers/shared/api/auth.php b/app/controllers/shared/api/auth.php index 5b1af0d36c..c381d9662a 100644 --- a/app/controllers/shared/api/auth.php +++ b/app/controllers/shared/api/auth.php @@ -32,7 +32,7 @@ App::init() break; case 'magic-url': - if ($project->getAttribute('usersAuthMagicURL', true) === false) { + if (($auths['usersAuthMagicURL'] ?? true) === false) { throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project'); } break; @@ -43,6 +43,12 @@ App::init() } break; + case 'phone': + if (($auths['phone'] ?? true) === false) { + throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project'); + } + break; + case 'invites': if (($auths['invites'] ?? true) === false) { throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project'); diff --git a/docker-compose.yml b/docker-compose.yml index 5c645e3bcd..395923681d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -571,6 +571,7 @@ services: - _APP_REDIS_PASS - _APP_SMS_PROVIDER - _APP_SMS_FROM + - _APP_SMS_PROJECTS_DENY_LIST - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 09e77c01ca..92f9e8fdf4 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -53,10 +53,20 @@ class Messaging extends Action */ public function action(Message $message): void { - var_dump($message); - $payload = $message->getPayload() ?? []; + if (empty($payload['project'])) { + throw new Exception('Project not set in payload'); + } + + Console::log($payload['project']['$id']); + $denyList = App::getEnv('_APP_SMS_PROJECTS_DENY_LIST', ''); + $denyList = explode(',', $denyList); + if (in_array($payload['project']['$id'], $denyList)) { + Console::error("Project is in the deny list. Skipping ..."); + return; + } + if (empty($payload)) { Console::error('Payload arg not found'); return;