29 - Security hardening: registration gate, input validation, nginx headers, env defaults, user model

This commit is contained in:
myrmidex 2026-05-02 16:14:31 +02:00
parent 27f0ac8568
commit b1d0ab793c
9 changed files with 39 additions and 24 deletions

View file

@ -1,7 +1,7 @@
APP_NAME=Laravel
APP_ENV=local
APP_ENV=production
APP_KEY=
APP_DEBUG=true
APP_DEBUG=false
APP_URL=http://localhost
APP_LOCALE=en
@ -18,18 +18,18 @@ BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
LOG_LEVEL=error
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=incr
DB_USERNAME=incr_user
DB_PASSWORD=incr_password
DB_PASSWORD=change_me_in_production
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_ENCRYPT=true
SESSION_PATH=/
SESSION_DOMAIN=null

View file

@ -21,6 +21,10 @@ class RegisteredUserController extends Controller
*/
public function create(): Response
{
if (User::exists()) {
abort(403, 'Registration is disabled.');
}
return Inertia::render('auth/register');
}
@ -31,16 +35,20 @@ public function create(): Response
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
if (User::exists()) {
abort(403, 'Registration is disabled.');
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
$user = User::forceCreate([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
event(new Registered($user));

View file

@ -12,15 +12,12 @@ class MilestoneController extends Controller
{
public function store(Request $request): RedirectResponse
{
$request->validate([
$validated = $request->validate([
'target' => 'required|integer|min:1',
'description' => 'required|string|max:255',
]);
Milestone::create([
'target' => $request->target,
'description' => $request->description,
]);
Milestone::create($validated);
return back()->with('success', 'Milestone created successfully');
}

View file

@ -46,13 +46,15 @@ public function update(Request $request)
public function history(Request $request): JsonResponse
{
$limit = $request->get('limit', 30);
$limit = min(max(1, $request->integer('limit', 30)), 365);
return response()->json(AssetPrice::history($this->user->asset_id, $limit));
}
public function forDate(Request $request, string $date): JsonResponse
{
validator(['date' => $date], ['date' => 'required|date_format:Y-m-d'])->validate();
return response()->json([
'date' => $date,
'price' => AssetPrice::forDate($date, $this->user->asset_id),

View file

@ -44,7 +44,7 @@ public function share(Request $request): array
'name' => config('app.name'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $request->user(),
'user' => $request->user()?->only(['id', 'name', 'email']),
],
'ziggy' => fn (): array => [
...(new Ziggy)->toArray(),

View file

@ -9,6 +9,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
/**
* @property int|null $asset_id
@ -26,7 +27,6 @@ class User extends Authenticatable
protected $fillable = [
'name',
'email',
'password',
'asset_id',
'price_tracking_enabled',
];
@ -57,10 +57,12 @@ public function asset(): BelongsTo
public static function default(): self
{
return self::firstOrCreate(
['email' => 'user@incr.local'],
['name' => 'Default User', 'password' => 'password']
);
return self::firstWhere('email', 'user@incr.local')
?? self::forceCreate([
'email' => 'user@incr.local',
'name' => 'Default User',
'password' => bcrypt(Str::random(32)),
]);
}
public function hasCompletedOnboarding(): bool

View file

@ -4,6 +4,12 @@ server {
root /var/www/html/public;
index index.php index.html;
server_tokens off;
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "strict-origin-when-cross-origin";
location / {
try_files $uri $uri/ /index.php?$query_string;
}

View file

@ -10,7 +10,7 @@ fi
# Wait for database to be ready
echo "Waiting for database..."
until php artisan tinker --execute="DB::connection()->getPdo();" 2>/dev/null; do
until mysql -h"${DB_HOST:-db}" -u"${DB_USERNAME:-incr_user}" -p"${DB_PASSWORD}" -e "SELECT 1" >/dev/null 2>&1; do
echo "Database not ready, waiting..."
sleep 2
done

View file

@ -1,6 +1,6 @@
[supervisord]
nodaemon=true
user=root
user=www-data
[program:nginx]
command=nginx -g "daemon off;"