diff --git a/app/Http/Controllers/GuestbookController.php b/app/Http/Controllers/GuestbookController.php new file mode 100644 index 0000000..aff30ed --- /dev/null +++ b/app/Http/Controllers/GuestbookController.php @@ -0,0 +1,29 @@ +validate($request, [ + 'name' => 'required', + 'message' => 'required' + ]); + + DB::insert('INSERT INTO guestbook_entries (name, timestamp, ip_address, agent, message) values (?, ?, ?, ?, ?)', array( + htmlspecialchars($request->get('name')), + time(), + $request->ip(), + $request->userAgent(), + htmlspecialchars($request->get('message')) + )); + + return back()->with('success', 'Entry submitted successfully!'); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 1fb53dc..4eab7b8 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -45,6 +45,11 @@ class Kernel extends HttpKernel ], ]; + protected $routeMiddleware = [ + 'rate_limit' => \App\Http\Middleware\RateLimiter::class, + ]; + + /** * The application's middleware aliases. * diff --git a/app/Http/Middleware/RateLimiter.php b/app/Http/Middleware/RateLimiter.php new file mode 100644 index 0000000..c81da43 --- /dev/null +++ b/app/Http/Middleware/RateLimiter.php @@ -0,0 +1,32 @@ +ip(); + $cacheKey = 'rate_limit_' . $ipAddress; + + if (Cache::has($cacheKey)) { + // If the cache key exists, the IP has submitted an entry within the last hour + return response()->view('errors.ratelimit-guestbook', [], 429); + } + + // Add the IP address to the cache and set the expiration time to one hour + Cache::put($cacheKey, true, 60); + + return $next($request); + } +} diff --git a/composer.json b/composer.json index e4c5e7d..451f135 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.10", "laravel/sanctum": "^3.2", - "laravel/tinker": "^2.8" + "laravel/tinker": "^2.8", + "spatie/laravel-honeypot": "^4.3" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 4664da4..3018272 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "aa322c53454393ed775cfe4807d54a50", + "content-hash": "505f8d503188864625fc855900ea2202", "packages": [ { "name": "brick/math", @@ -2994,6 +2994,142 @@ ], "time": "2023-04-15T23:01:58+00:00" }, + { + "name": "spatie/laravel-honeypot", + "version": "4.3.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-honeypot.git", + "reference": "eab92dd2096f1cdb83c28ced4f4632d3cfde2872" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-honeypot/zipball/eab92dd2096f1cdb83c28ced4f4632d3cfde2872", + "reference": "eab92dd2096f1cdb83c28ced4f4632d3cfde2872", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0|^9.0|^10.0", + "illuminate/encryption": "^8.0|^9.0|^10.0", + "illuminate/http": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0", + "illuminate/validation": "^8.0|^9.0|^10.0", + "nesbot/carbon": "^2.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.9", + "symfony/http-foundation": "^5.1.2|^6.0" + }, + "require-dev": { + "livewire/livewire": "^2.10", + "orchestra/testbench": "^6.23|^7.0|^8.0", + "pestphp/pest-plugin-livewire": "^1.0", + "phpunit/phpunit": "^9.4", + "spatie/pest-plugin-snapshots": "^1.1", + "spatie/phpunit-snapshot-assertions": "^4.2", + "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.3.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2023-01-17T07:09:34+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "efab1844b8826443135201c4443690f032c3d533" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/efab1844b8826443135201c4443690f032c3d533", + "reference": "efab1844b8826443135201c4443690f032c3d533", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28|^10.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0", + "pestphp/pest": "^1.22", + "phpunit/phpunit": "^9.5.24", + "spatie/pest-plugin-test-time": "^1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-04-27T08:09:01+00:00" + }, { "name": "symfony/console", "version": "v6.3.0", diff --git a/config/database.php b/config/database.php index 137ad18..67ae798 100644 --- a/config/database.php +++ b/config/database.php @@ -34,15 +34,6 @@ */ 'connections' => [ - - 'sqlite' => [ - 'driver' => 'sqlite', - 'url' => env('DATABASE_URL'), - 'database' => env('DB_DATABASE', database_path('database.sqlite')), - 'prefix' => '', - 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), - ], - 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), @@ -62,37 +53,6 @@ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], - - 'pgsql' => [ - 'driver' => 'pgsql', - 'url' => env('DATABASE_URL'), - 'host' => env('DB_HOST', '127.0.0.1'), - 'port' => env('DB_PORT', '5432'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'prefix_indexes' => true, - 'search_path' => 'public', - 'sslmode' => 'prefer', - ], - - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'url' => env('DATABASE_URL'), - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', '1433'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'prefix_indexes' => true, - // 'encrypt' => env('DB_ENCRYPT', 'yes'), - // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), - ], - ], /* @@ -107,45 +67,4 @@ */ 'migrations' => 'migrations', - - /* - |-------------------------------------------------------------------------- - | Redis Databases - |-------------------------------------------------------------------------- - | - | Redis is an open source, fast, and advanced key-value store that also - | provides a richer body of commands than a typical key-value system - | such as APC or Memcached. Laravel makes it easy to dig right in. - | - */ - - 'redis' => [ - - 'client' => env('REDIS_CLIENT', 'phpredis'), - - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - ], - - 'default' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - ], - - 'cache' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_CACHE_DB', '1'), - ], - - ], - ]; diff --git a/public/css/master.css b/public/css/master.css index 323c2ec..7cadfe2 100644 --- a/public/css/master.css +++ b/public/css/master.css @@ -115,6 +115,10 @@ div.preview pre.small, div.project pre.small { div.preview pre, div.project pre { background-color: #222; color: #ccc; + display: inline-block; + text-align: left; + padding: 0.2em; + max-width: 90% } div.project pre { @@ -283,13 +287,6 @@ .header { color: #FFFFFF; } -div.preview pre, div.project pre { - display: inline-block; - text-align: left; - padding: 0.2em; - max-width: 90% -} - h1 { font-size: 150% } h2 { font-size: 130% } h3 { font-size: 115% } @@ -324,3 +321,32 @@ a { color: #99f; text-decoration: none } + +table.gb_entryform tr td { + border: none; +} + +table.gb_entryform tr td label { + padding-right: 5px; +} + +table.gb_entryform tr td span.text-danger { + padding-left: 5px; + color: rgb(255, 114, 114); +} + +table.gb_entryform tr td textarea, +table.gb_entryform tr td input { + margin-bottom: 5px; +} + +table.gb_entry tr td { + border: solid #FFFFFF; + width: 500px; + vertical-align: top; + padding: 5px; +} + +table.gb_entry { + margin-bottom: 5px; +} diff --git a/public/css/minimal.css b/public/css/minimal.css new file mode 100644 index 0000000..3362057 --- /dev/null +++ b/public/css/minimal.css @@ -0,0 +1,11 @@ +html { + color-scheme: dark; +} + +body { + font-family: sans-serif; + margin: 0px; + margin-left: 10px; + color: #ddd; + background-color: #333; +} diff --git a/resources/views/errors/ratelimit-guestbook.blade.php b/resources/views/errors/ratelimit-guestbook.blade.php new file mode 100644 index 0000000..fa29855 --- /dev/null +++ b/resources/views/errors/ratelimit-guestbook.blade.php @@ -0,0 +1,16 @@ + + + + Error 429: Overclocking Detected! + + + + +

Error 429: Overclocking Detected!

+
+

Whoa there! Your submissions are going at warp speed.

+

Remember you can only submit an entry once every hour!

+
+ Click here to go back to the guestbook. + + diff --git a/resources/views/includes/header.blade.php b/resources/views/includes/header.blade.php index 2951003..53d44e2 100644 --- a/resources/views/includes/header.blade.php +++ b/resources/views/includes/header.blade.php @@ -6,6 +6,7 @@ projects | calculators | computers | - bookmarks + bookmarks | + guestbook diff --git a/resources/views/pages/guestbook.blade.php b/resources/views/pages/guestbook.blade.php new file mode 100644 index 0000000..f6b758a --- /dev/null +++ b/resources/views/pages/guestbook.blade.php @@ -0,0 +1,53 @@ +@extends('layouts.default') +@section('title', 'guestbook') +@section('content') +
+
+ @csrf + + + + + + + + + + + + + + + +
+ + + + + {{ $errors->first('name') }} +
+ + + + + {{ $errors->first('message') }} +
+ +
+ +

You can submit an entry once every hour.

+

Your IP address will be logged but will not be publically displayed.

+
+ @php + $entries = DB::select('SELECT name, timestamp, message FROM guestbook_entries ORDER BY id DESC'); + @endphp +

Entries ({{ count($entries) }} total)

+ @foreach ($entries as $entry) +
+ Name: {{ $entry->name }}
+ Date: {{ gmdate("H:i:s - Y-m-d", $entry->timestamp) }}

+ {{ htmlspecialchars($entry->message) }} +
+ @endforeach +@stop + diff --git a/routes/web.php b/routes/web.php index 055b4e5..6185449 100644 --- a/routes/web.php +++ b/routes/web.php @@ -32,3 +32,12 @@ Route::get('/computers', function () { return View::make('pages.computers'); }); + +Route::get('/guestbook', 'App\Http\Controllers\GuestbookController@guestbook') + ->name('guestbook'); + +Route::post('/guestbook', 'App\Http\Controllers\GuestbookController@guestbookpost') + ->name('guestbookPost') + ->middleware('rate_limit'); + +