Replace custom register token with spatie/laravel-honeypot

Swap the custom 'rt' register token anti-spam mechanism for
spatie/laravel-honeypot on the registration and parental-controls
invite flows.

- Add spatie/laravel-honeypot and publish config/honeypot.php
- Remove getRegisterToken() and the rt validation rule from RegisterController
- Replace the rt hidden field with the @honeypot directive in both forms
- Attach ProtectAgainstSpam middleware to POST /register and the
  parental-controls invite register route
- Update RegisterTest to disable honeypot for the valid registration case
pull/7108/head
Your Name 2 weeks ago
parent ac34213121
commit ce4343e3e2

@ -13,10 +13,8 @@ use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Purify;
class RegisterController extends Controller
@ -51,13 +49,6 @@ class RegisterController extends Controller
$this->middleware('guest');
}
public function getRegisterToken()
{
return Cache::remember('pf:register:rt', 900, function () {
return Str::random(40);
});
}
/**
* Get a validator for an incoming registration request.
*
@ -93,18 +84,8 @@ class RegisterController extends Controller
},
];
$rt = [
'required',
function ($attribute, $value, $fail) {
if ($value !== $this->getRegisterToken()) {
return $fail('Something went wrong');
}
},
];
$rules = [
'agecheck' => 'required|accepted',
'rt' => $rt,
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
'username' => $usernameRules,
'email' => $emailRules,

@ -43,6 +43,7 @@
"pusher/pusher-php-server": "^7.2",
"resend/resend-php": "^0.13.0",
"spatie/laravel-backup": "^9.2.9",
"spatie/laravel-honeypot": "^4.7",
"spatie/laravel-image-optimizer": "^1.8.2",
"stevebauman/purify": "^6.2.0",
"symfony/http-client": "^7.3",

78
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d438c452baff3ac7487931d7bbbf2d68",
"content-hash": "418cf8d4002b49158c5ea0b6bd4dcf4e",
"packages": [
{
"name": "aws/aws-crt-php",
@ -6813,6 +6813,82 @@
],
"time": "2026-02-15T19:05:20+00:00"
},
{
"name": "spatie/laravel-honeypot",
"version": "4.7.2",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-honeypot.git",
"reference": "b99f668c5b673ddc8abbc58743584339cc8db3a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-honeypot/zipball/b99f668c5b673ddc8abbc58743584339cc8db3a9",
"reference": "b99f668c5b673ddc8abbc58743584339cc8db3a9",
"shasum": ""
},
"require": {
"illuminate/contracts": "^11.0|^12.0|^13.0",
"illuminate/encryption": "^11.0|^12.0|^13.0",
"illuminate/http": "^11.0|^12.0|^13.0",
"illuminate/support": "^11.0|^12.0|^13.0",
"illuminate/validation": "^11.0|^12.0|^13.0",
"nesbot/carbon": "^2.0|^3.0",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.9",
"symfony/http-foundation": "^7.0|^8.0"
},
"require-dev": {
"livewire/livewire": "^3.0|^4.0",
"orchestra/testbench": "^9.0|^10.0|^11.0",
"pestphp/pest": "^2.0|^3.0|^4.0",
"pestphp/pest-plugin-livewire": "^1.0|^2.1|^3.0|^4.0",
"spatie/pest-plugin-snapshots": "^1.1|^2.1",
"spatie/phpunit-snapshot-assertions": "^4.2|^5.1",
"spatie/test-time": "^1.2.1"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Honeypot\\HoneypotServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\Honeypot\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Preventing spam submitted through forms",
"homepage": "https://github.com/spatie/laravel-honeypot",
"keywords": [
"laravel-honeypot",
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-honeypot/tree/4.7.2"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
}
],
"time": "2026-06-25T15:09:40+00:00"
},
{
"name": "spatie/laravel-image-optimizer",
"version": "1.8.3",

@ -0,0 +1,72 @@
<?php
use Spatie\Honeypot\SpamProtection;
use Spatie\Honeypot\SpamResponder\BlankPageResponder;
return [
/*
* This switch determines if the honeypot protection should be activated.
*/
'enabled' => env('HONEYPOT_ENABLED', true),
/*
* Here you can specify name of the honeypot field. Any requests that submit a non-empty
* value for this name will be discarded. Make sure this name does not
* collide with a form field that is actually used.
*/
'name_field_name' => env('HONEYPOT_NAME', 'my_name'),
/*
* When this is activated there will be a random string added
* to the name_field_name. This improves the
* protection against bots.
*/
'randomize_name_field_name' => env('HONEYPOT_RANDOMIZE', true),
/*
* When this is activated, requests will be checked if
* form is submitted faster than this amount of seconds
*/
'valid_from_timestamp' => env('HONEYPOT_VALID_FROM_TIMESTAMP', true),
/*
* This field contains the name of a form field that will be used to verify
* if the form wasn't submitted too quickly. Make sure this name does not
* collide with a form field that is actually used.
*/
'valid_from_field_name' => env('HONEYPOT_VALID_FROM', 'valid_from'),
/*
* If the form is submitted faster than this amount of seconds
* the form submission will be considered invalid.
*/
'amount_of_seconds' => (int) env('HONEYPOT_SECONDS', 1),
/*
* This class is responsible for sending a response to requests that
* are detected as being spammy. By default a blank page is shown.
*
* A valid responder is any class that implements
* `Spatie\Honeypot\SpamResponder\SpamResponder`
*/
'respond_to_spam_with' => BlankPageResponder::class,
/*
* When activated, requests will be checked if honeypot fields are missing,
* if so the request will be stamped as spam. Be careful! When using the
* global middleware be sure to add honeypot fields to each form.
*/
'honeypot_fields_required_for_all_forms' => false,
/*
* This class is responsible for applying all spam protection
* rules for a request. In most cases, you shouldn't change
* this value.
*/
'spam_protection' => SpamProtection::class,
/*
* need to add @cspNonce https://github.com/spatie/laravel-csp in style tag hidden items
*/
'with_csp' => env('HONEYPOT_WITH_CSP', false),
];

@ -10,7 +10,7 @@
<div class="card-body">
<form method="POST" action="{{ route('register') }}" class="px-md-3">
@csrf
<input type="hidden" name="rt" value="{{ (new \App\Http\Controllers\Auth\RegisterController())->getRegisterToken() }}">
@honeypot
<div class="form-group row">
<div class="col-md-12">
<label class="small font-weight-bold text-lighter">{{ __('auth.name') }}</label>

@ -32,8 +32,7 @@
<div class="card-body">
<form method="POST" class="px-md-3">
@csrf
<input type="hidden" name="rt" value="{{ (new \App\Http\Controllers\Auth\RegisterController())->getRegisterToken() }}">
@honeypot
<div class="form-group row">
<div class="col-md-12">
<label class="small font-weight-bold text-lighter">Name</label>

@ -4,6 +4,7 @@ use App\Http\Controllers\AccountController;
use App\Http\Controllers\AccountInterstitialController;
use App\Http\Controllers\AdminInviteController;
use App\Http\Controllers\AppRegisterController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\AuthorizeInteractionController;
use App\Http\Controllers\AvatarController;
use App\Http\Controllers\BookmarkController;
@ -53,6 +54,7 @@ use Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController;
use Laravel\Passport\Http\Controllers\ClientController;
use Laravel\Passport\Http\Controllers\DenyAuthorizationController;
use Laravel\Passport\Http\Controllers\TransientTokenController;
use Spatie\Honeypot\ProtectAgainstSpam;
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
Route::get('/', [SiteController::class, 'home'])->name('timeline.personal');
@ -63,6 +65,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Auth::routes();
Route::post('register', [RegisterController::class, 'register'])->middleware(ProtectAgainstSpam::class);
Route::get('auth/oidc/start', [RemoteOidcController::class, 'start']);
Route::get('auth/oidc/callback', [RemoteOidcController::class, 'handleCallback']);
@ -86,7 +90,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('auth/raw/mastodon/s/finish-up', [RemoteAuthController::class, 'finishUp']);
Route::post('auth/raw/mastodon/s/login', [RemoteAuthController::class, 'handleLogin']);
Route::get('auth/pci/{id}/{code}', [ParentalControlsController::class, 'inviteRegister']);
Route::post('auth/pci/{id}/{code}', [ParentalControlsController::class, 'inviteRegisterStore']);
Route::post('auth/pci/{id}/{code}', [ParentalControlsController::class, 'inviteRegisterStore'])->middleware(ProtectAgainstSpam::class);
Route::get('auth/sign_up', [SiteController::class, 'curatedOnboarding'])->name('auth.curated-onboarding');
Route::post('auth/sign_up', [CuratedRegisterController::class, 'proceed']);

@ -23,9 +23,9 @@ it('creates a user with valid registration data', function () {
config(['pixelfed.max_users' => 1000]);
config(['instance.enable_cc' => false]);
// Visit the register page first to seed the RT token in cache
$this->get('/register')->assertOk();
$rt = cache()->get('pf:register:rt');
// Disable the honeypot spam protection so the test submission isn't
// flagged for being submitted faster than the minimum timestamp threshold.
config(['honeypot.enabled' => false]);
$response = $this->post('/register', [
'name' => 'Test User',
@ -35,7 +35,6 @@ it('creates a user with valid registration data', function () {
'password_confirmation' => 'SecurePass123!',
'agree' => 'on',
'agecheck' => 'on',
'rt' => $rt,
]);
$response->assertRedirect();

Loading…
Cancel
Save