diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -92,6 +92,18 @@ return response()->json(['status' => 'error', 'errors' => $errors], 422); } + // Throttling signup requests from the same IP/Network + $limit = 3; + $minutes = 10; + $count = SignupCode::withTrashed() + ->where('created_at', '>=', now()->subMinutes($minutes)->toDateTimeString()) + ->where('ip_address', \App\Utils::requestIp()) + ->count(); + + if ($count > $limit) { + return $this->errorResponse(429, \trans('app.toomanyrequests')); + } + // Generate the verification code $code = SignupCode::create([ 'data' => [ diff --git a/src/app/Http/Controllers/Controller.php b/src/app/Http/Controllers/Controller.php --- a/src/app/Http/Controllers/Controller.php +++ b/src/app/Http/Controllers/Controller.php @@ -30,8 +30,9 @@ 401 => "Unauthorized", 403 => "Access denied", 404 => "Not found", - 422 => "Input validation error", 405 => "Method not allowed", + 422 => "Input validation error", + 429 => "Too many requests", 500 => "Internal server error", ]; diff --git a/src/app/Observers/SignupCodeObserver.php b/src/app/Observers/SignupCodeObserver.php --- a/src/app/Observers/SignupCodeObserver.php +++ b/src/app/Observers/SignupCodeObserver.php @@ -35,5 +35,6 @@ } $code->expires_at = Carbon::now()->addHours($exp_hours); + $code->ip_address = \App\Utils::requestIp(); } } diff --git a/src/app/SignupCode.php b/src/app/SignupCode.php --- a/src/app/SignupCode.php +++ b/src/app/SignupCode.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; /** * The eloquent definition of a SignupCode. @@ -15,6 +16,8 @@ */ class SignupCode extends Model { + use SoftDeletes; + public const SHORTCODE_LENGTH = 5; public const CODE_LENGTH = 32; diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -203,8 +203,7 @@ } // Convert the hexadecimal string to a binary string - # Using pack() here - # Newer PHP version can use hex2bin() + // Using pack() here, newer PHP version can use hex2bin() $lastaddrbin = pack('H*', $lastAddrHex); // And create an IPv6 address from the binary string @@ -284,6 +283,17 @@ return implode($join, $randStrs); } + /** + * Returns the IP address of the HTTP request + * + * @return string IP address + */ + public static function requestIp() + { + // TODO: Verify if it works with proxies + return request()->ip(); + } + /** * Returns a UUID in the form of an integer. * diff --git a/src/database/migrations/2021_03_25_100000_add_signup_code_ip_address.php b/src/database/migrations/2021_03_25_100000_add_signup_code_ip_address.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2021_03_25_100000_add_signup_code_ip_address.php @@ -0,0 +1,45 @@ +softDeletes(); + $table->timestamp('created_at')->useCurrent(); + $table->string('ip_address')->nullable(); + + $table->index(['created_at', 'ip_address']); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + 'signup_codes', + function (Blueprint $table) { + $table->dropColumn('created_at'); + $table->dropColumn('deleted_at'); + $table->dropColumn('ip_address'); + } + ); + } +} diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php --- a/src/resources/lang/en/app.php +++ b/src/resources/lang/en/app.php @@ -48,6 +48,8 @@ 'support-request-success' => 'Support request submitted successfully.', 'support-request-error' => 'Failed to submit the support request.', + 'toomanyrequests' => 'Too many requests. Hold on for a while.', + 'wallet-award-success' => 'The bonus has been added to the wallet successfully.', 'wallet-penalty-success' => 'The penalty has been added to the wallet successfully.', 'wallet-update-success' => 'User wallet updated successfully.',