Fix email abuse limit

This commit is contained in:
Matej Bačo
2026-03-27 15:51:13 +01:00
parent f2c8500d73
commit d2172033dc
5 changed files with 38 additions and 11 deletions
+8 -8
View File
@@ -962,7 +962,7 @@ Http::patch('/v1/account/sessions/:sessionId')
Http::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create email password session')
->groups(['api', 'account', 'auth', 'session'])
->groups(['api', 'account', 'auth', 'session', 'outgoingEmail'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'sessions.write')
->label('auth.type', 'email-password')
@@ -1269,7 +1269,7 @@ Http::post('/v1/account/sessions/anonymous')
Http::post('/v1/account/sessions/token')
->desc('Create session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
->groups(['api', 'account', 'session', 'queueForMails'])
->label('scope', 'sessions.write')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{response.userId}')
@@ -2115,7 +2115,7 @@ Http::get('/v1/account/tokens/oauth2/:provider')
Http::post('/v1/account/tokens/magic-url')
->alias('/v1/account/sessions/magic-url')
->desc('Create magic URL token')
->groups(['api', 'account', 'auth'])
->groups(['api', 'account', 'auth', 'outgoingEmail'])
->label('scope', 'sessions.write')
->label('auth.type', 'magic-url')
->label('audits.event', 'session.create')
@@ -2395,7 +2395,7 @@ Http::post('/v1/account/tokens/magic-url')
Http::post('/v1/account/tokens/email')
->desc('Create email token (OTP)')
->groups(['api', 'account', 'auth'])
->groups(['api', 'account', 'auth', 'outgoingEmail'])
->label('scope', 'sessions.write')
->label('auth.type', 'email-otp')
->label('audits.event', 'session.create')
@@ -2698,7 +2698,7 @@ Http::post('/v1/account/tokens/email')
Http::put('/v1/account/sessions/magic-url')
->desc('Update magic URL session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
->groups(['api', 'account', 'session', 'outgoingEmail'])
->label('scope', 'sessions.write')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{response.userId}')
@@ -2748,7 +2748,7 @@ Http::put('/v1/account/sessions/magic-url')
Http::put('/v1/account/sessions/phone')
->desc('Update phone session')
->label('event', 'users.[userId].sessions.[sessionId].create')
->groups(['api', 'account', 'session'])
->groups(['api', 'account', 'session', 'outgoingEmail'])
->label('scope', 'sessions.write')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{response.userId}')
@@ -3575,7 +3575,7 @@ Http::patch('/v1/account/status')
Http::post('/v1/account/recovery')
->desc('Create password recovery')
->groups(['api', 'account'])
->groups(['api', 'account', 'outgoingEmail'])
->label('scope', 'sessions.write')
->label('event', 'users.[userId].recovery.[tokenId].create')
->label('audits.event', 'recovery.create')
@@ -3869,7 +3869,7 @@ Http::put('/v1/account/recovery')
Http::post('/v1/account/verifications/email')
->alias('/v1/account/verification')
->desc('Create email verification')
->groups(['api', 'account'])
->groups(['api', 'account', 'outgoingEmail'])
->label('scope', 'account')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
+1 -1
View File
@@ -1480,7 +1480,7 @@ Http::patch('/v1/projects/:projectId/smtp')
Http::post('/v1/projects/:projectId/smtp/tests')
->desc('Create SMTP test')
->groups(['api', 'projects'])
->groups(['api', 'projects', 'outgoingEmail'])
->label('scope', 'projects.write')
->label('sdk', [
new Method(
+27
View File
@@ -37,6 +37,7 @@ use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Table;
use Utopia\Abuse\Abuse;
use Utopia\Bus\Bus;
use Utopia\Config\Config;
use Utopia\Console;
@@ -1126,6 +1127,32 @@ Http::init()
});
});
/**
* Email worker abuse protection
*/
Http::init()
->groups(['outgoingEmail'])
->inject('project')
->inject('timelimit')
->action(function (Document $project, $timelimit) {
/*
* Abuse Check
*/
$abuseKey = 'initHook:outgoingEmail,projectId:{projectId}';
$timeLimit = $timelimit($abuseKey, limit: 1000, time: 3600); // 1000 emails per hour , per project
$timeLimit
->setParam('{projectId}', $project->getId());
$abuse = new Abuse($timeLimit);
$enabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
if ($enabled && $abuse->check()) {
throw new AppwriteException(AppwriteException::GENERAL_RATE_LIMIT_EXCEEDED);
}
});
Http::options()
->inject('utopia')
->inject('swooleRequest')
@@ -49,7 +49,7 @@ class Create extends Action
->setHttpPath('/v1/account/mfa/challenges')
->httpAlias('/v1/account/mfa/challenge')
->desc('Create MFA challenge')
->groups(['api', 'account', 'mfa'])
->groups(['api', 'account', 'mfa', 'queueForMails'])
->label('scope', 'account')
->label('event', 'users.[userId].challenges.[challengeId].create')
->label('audits.event', 'challenge.create')
@@ -53,7 +53,7 @@ class Create extends Action
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/teams/:teamId/memberships')
->desc('Create team membership')
->groups(['api', 'teams', 'auth'])
->groups(['api', 'teams', 'auth', 'queueForMails'])
->label('event', 'teams.[teamId].memberships.[membershipId].create')
->label('scope', 'teams.write')
->label('auth.type', 'invites')