Skip to content

Commit f820fe7

Browse files
committed
first commit
0 parents  commit f820fe7

9 files changed

Lines changed: 285 additions & 0 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/vendor/
2+
/.idea/
3+
/.vscode/
4+
composer.lock

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Changelog
2+
3+
All notable changes to **darvis/livewire-honeypot** are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [1.0.0] - 2025-10-23
9+
### Added
10+
- Initial release with:
11+
- Livewire Trait `HasHoneypot`
12+
- Service `HoneypotService`
13+
- Blade component `<x-honeypot />`
14+
- View publishing, Laravel auto‑discovery

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Darvis
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# darvis/livewire-honeypot
2+
3+
Lightweight **honeypot + time‑trap** protection for **Livewire 3** (Laravel 11).
4+
Blocks simple bots without CAPTCHAs, privacy‑friendly and unobtrusive.
5+
6+
## Features
7+
- 🪤 Honeypot bait field (`present|size:0`)
8+
- ⏱️ Time‑trap (minimum fill time)
9+
- 🧩 Works as **Trait** for Livewire and as **Service** for controllers/APIs
10+
- 🧱 Blade component `<x-honeypot />` for easy inclusion
11+
- 🔌 Zero dependencies beyond Livewire 3 / Laravel 11
12+
13+
## Installation
14+
15+
```bash
16+
composer require darvis/livewire-honeypot
17+
```
18+
19+
(For local development, you can add a `path` repository in your app's `composer.json`.)
20+
21+
## Usage — Livewire (Trait)
22+
23+
1) In your Livewire component:
24+
25+
```php
26+
use Darvis\LivewireHoneypot\Traits\HasHoneypot;
27+
28+
class ContactForm extends Component
29+
{
30+
use HasHoneypot;
31+
32+
public string $name = '';
33+
public string $email = '';
34+
public string $message = '';
35+
36+
public function submit(): void
37+
{
38+
$this->validate([
39+
'name' => 'required|string|min:2',
40+
'email' => 'required|email',
41+
'message' => 'required|string|min:10',
42+
]);
43+
44+
$this->validateHoneypot();
45+
46+
// process form ...
47+
48+
$this->reset(['name','email','message']);
49+
$this->resetHoneypot();
50+
}
51+
}
52+
```
53+
54+
2) In your Blade (or Flux) view, add the component (place anywhere inside the form):
55+
56+
```blade
57+
<x-honeypot />
58+
```
59+
60+
## Usage — Controller / API (Service)
61+
62+
```php
63+
use Darvis\LivewireHoneypot\Services\HoneypotService;
64+
65+
public function store(Request $request, HoneypotService $honeypot)
66+
{
67+
$honeypot->validate($request->only('hp_website', 'hp_started_at', 'hp_token'));
68+
// process form ...
69+
}
70+
```
71+
72+
To generate fields server‑side (non‑Livewire forms):
73+
74+
```php
75+
$hp = app(Darvis\LivewireHoneypot\Services\HoneypotService::class)->generate();
76+
// pass $hp to your view to prefill hidden inputs
77+
```
78+
79+
## Publishing the Blade view (optional)
80+
```bash
81+
php artisan vendor:publish --tag=livewire-honeypot-views
82+
```
83+
84+
## Throttling (recommended)
85+
Add request rate‑limiting on your form route:
86+
87+
```php
88+
Route::get('/contact', \App\Livewire\ContactForm::class)->middleware('throttle:10,1');
89+
```
90+
91+
## License
92+
MIT © Arvid de Jong (info@arvid.nl)

composer.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "darvis/livewire-honeypot",
3+
"description": "Lightweight honeypot + time-trap protection for Livewire 3 forms",
4+
"type": "library",
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-4": {
8+
"Darvis\\LivewireHoneypot\\": "src/"
9+
}
10+
},
11+
"extra": {
12+
"laravel": {
13+
"providers": [
14+
"Darvis\\LivewireHoneypot\\HoneypotServiceProvider"
15+
]
16+
}
17+
},
18+
"require": {
19+
"php": "^8.2",
20+
"laravel/framework": "^11.0",
21+
"livewire/livewire": "^3.0"
22+
},
23+
"minimum-stability": "stable",
24+
"prefer-stable": true
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{{-- Anonymous honeypot component. Usage: <x-honeypot /> --}}
2+
<div class="hp-field" aria-hidden="true">
3+
<label>
4+
<span>Website (leave empty)</span>
5+
<input type="text"
6+
name="hp_website"
7+
{{ $attributes->whereStartsWith('wire:model')->first() ? '' : 'wire:model.lazy=hp_website' }}
8+
tabindex="-1"
9+
autocomplete="off" />
10+
</label>
11+
<input type="hidden" name="hp_started_at" {{ $attributes->whereStartsWith('wire:model')->first() ? '' : 'wire:model=hp_started_at' }}>
12+
<input type="hidden" name="hp_token" {{ $attributes->whereStartsWith('wire:model')->first() ? '' : 'wire:model=hp_token' }}>
13+
14+
<style>
15+
.hp-field {
16+
position: absolute !important;
17+
left: -10000px !important;
18+
top: auto !important;
19+
width: 1px !important;
20+
height: 1px !important;
21+
overflow: hidden !important;
22+
}
23+
</style>
24+
</div>

src/HoneypotServiceProvider.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Darvis\LivewireHoneypot;
4+
5+
use Illuminate\Support\ServiceProvider;
6+
use Illuminate\Support\Facades\Blade;
7+
8+
class HoneypotServiceProvider extends ServiceProvider
9+
{
10+
public function boot(): void
11+
{
12+
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'livewire-honeypot');
13+
14+
// Register <x-honeypot />
15+
Blade::component('livewire-honeypot::components.honeypot', 'honeypot');
16+
17+
// Allow publishing the views
18+
$this->publishes([
19+
__DIR__ . '/../resources/views' => resource_path('views/vendor/livewire-honeypot'),
20+
], 'livewire-honeypot-views');
21+
}
22+
}

src/Services/HoneypotService.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Darvis\LivewireHoneypot\Services;
4+
5+
use Illuminate\Validation\ValidationException;
6+
use Illuminate\Support\Str;
7+
8+
class HoneypotService
9+
{
10+
public function generate(): array
11+
{
12+
return [
13+
'hp_website' => '',
14+
'hp_started_at' => now()->getTimestamp(),
15+
'hp_token' => Str::random(24),
16+
];
17+
}
18+
19+
public function validate(array $data, int $minimumSeconds = 3): void
20+
{
21+
validator($data, [
22+
'hp_website' => 'present|size:0',
23+
'hp_started_at' => 'required|integer',
24+
'hp_token' => 'required|string|min:10',
25+
], [
26+
'hp_website.size' => 'Spam detected.',
27+
])->validate();
28+
29+
$elapsed = now()->getTimestamp() - (int)($data['hp_started_at'] ?? 0);
30+
if ($elapsed < $minimumSeconds) {
31+
throw ValidationException::withMessages([
32+
'hp_website' => 'Form submitted too quickly.',
33+
]);
34+
}
35+
}
36+
}

src/Traits/HasHoneypot.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Darvis\LivewireHoneypot\Traits;
4+
5+
use Illuminate\Support\Str;
6+
use Illuminate\Validation\ValidationException;
7+
8+
trait HasHoneypot
9+
{
10+
public string $hp_website = '';
11+
public int $hp_started_at = 0;
12+
public string $hp_token = '';
13+
14+
protected int $minimumFillSeconds = 3;
15+
16+
public function initializeHasHoneypot(): void
17+
{
18+
$this->resetHoneypot();
19+
}
20+
21+
protected function resetHoneypot(): void
22+
{
23+
$this->hp_website = '';
24+
$this->hp_started_at = now()->getTimestamp();
25+
$this->hp_token = Str::random(24);
26+
}
27+
28+
protected function validateHoneypot(): void
29+
{
30+
// Require presence & emptiness of the bait field, plus meta fields
31+
$this->validate([
32+
'hp_website' => 'present|size:0',
33+
'hp_started_at' => 'required|integer',
34+
'hp_token' => 'required|string|min:10',
35+
], [
36+
'hp_website.size' => 'Spam detected.',
37+
]);
38+
39+
// Time-trap: minimum time spent before submit
40+
$elapsed = now()->getTimestamp() - (int) $this->hp_started_at;
41+
if ($elapsed < $this->minimumFillSeconds) {
42+
throw ValidationException::withMessages([
43+
'hp_website' => 'Form submitted too quickly.',
44+
]);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)