Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Providers;
use App\Auth\EncryptedUserProvider;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->configureDefaults();
Auth::provider('encrypted', function ($app, array $config) {
return new EncryptedUserProvider($app['hash'], $config['model']);
});
}
/**
* Configure default behaviors for production-ready applications.
*/
protected function configureDefaults(): void
{
Date::use(CarbonImmutable::class);
DB::prohibitDestructiveCommands(
app()->isProduction(),
);
Password::defaults(fn (): ?Password => app()->isProduction()
? Password::min(8)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised()
: null
);
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Config;
use Inertia\Inertia;
use Illuminate\Support\Facades\Cache;
use Laravel\Fortify\Features;
use Laravel\Fortify\Fortify;
use App\Notifications\NewLoginDetected;
use App\Models\UserRestriction;
use Illuminate\Support\Facades\Log;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
\Laravel\Fortify\Fortify::username('email');
Fortify::authenticateUsing(function (Request $request) {
$rawLogin = $request->input('email') ?? $request->input('login');
if (!$rawLogin) {
return null;
}
Log::info("LOGIN: Attempting login for '$rawLogin'");
$lowerLogin = strtolower($rawLogin);
$loginHash = hash('sha256', $rawLogin);
$lowerLoginHash = hash('sha256', $lowerLogin);
$user = User::where(function ($query) use ($rawLogin, $lowerLogin, $loginHash, $lowerLoginHash) {
$query->where('email', $rawLogin) // Case-sensitive match
->orWhere('username', $rawLogin) // Case-sensitive match
->orWhereRaw('LOWER(email) = ?', [$lowerLogin]) // Case-insensitive match
->orWhereRaw('LOWER(username) = ?', [$lowerLogin]) // Case-insensitive match
->orWhere('email_index', $loginHash) // Original hash match
->orWhere('email_index', $lowerLoginHash) // Lowercase hash match
->orWhere('username_index', $loginHash) // Original hash match
->orWhere('username_index', $lowerLoginHash); // Lowercase hash match
})->first();
if ($user) {
Log::info("LOGIN: User found (ID: {$user->id}). Checking password...");
$passwordMatches = false;
// 1. Standard Bcrypt check
if (Hash::check($request->password, $user->password)) {
Log::info("LOGIN: Password matched using Bcrypt (Correct).");
$passwordMatches = true;
}
// 2. Plaintext check (and upgrade)
elseif ($user->password === $request->password) {
Log::warning("LOGIN: Plaintext password matched for User ID {$user->id}. Upgrading hash now.");
$passwordMatches = true;
$user->forceFill(['password' => Hash::make($request->password)])->save();
}
// 3. SHA256 check (and upgrade)
elseif (hash('sha256', $request->password) === $user->password) {
Log::warning("LOGIN: SHA256 password matched for User ID {$user->id}. This is insecure. Upgrading hash now.");
$passwordMatches = true;
$user->forceFill(['password' => Hash::make($request->password)])->save();
}
// 4. MD5 check (and upgrade)
elseif (hash('md5', $request->password) === $user->password) {
Log::warning("LOGIN: MD5 password matched for User ID {$user->id}. This is insecure. Upgrading hash now.");
$passwordMatches = true;
$user->forceFill(['password' => Hash::make($request->password)])->save();
}
if ($passwordMatches) {
Log::info("LOGIN: Password check successful for User ID {$user->id}.");
if (UserRestriction::where('user_id', $user->id)->where('type', 'account_ban')->active()->exists()) {
Log::warning("LOGIN: Login blocked for banned User ID {$user->id}.");
return null;
}
$user->forceFill([
'last_login_at' => now(),
'last_login_ip' => $request->ip(),
'last_login_user_agent' => $request->userAgent() ?? '',
])->save();
if ($user->stats) {
$user->stats()->increment('total_logins');
}
return $user;
} else {
Log::error("LOGIN: All password checks failed for User ID {$user->id}.");
}
} else {
Log::error("LOGIN: User with identifier '$rawLogin' not found in database.");
}
return null;
});
$this->configureActions();
$this->configureViews();
$this->configureRateLimiting();
}
private function configureActions(): void
{
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::createUsersUsing(CreateNewUser::class);
}
private function configureViews(): void
{
Fortify::loginView(fn (Request $request) => Inertia::render('auth/Login', [
'canResetPassword' => Features::enabled(Features::resetPasswords()),
'canRegister' => Features::enabled(Features::registration()),
'status' => $request->session()->get('status'),
]));
Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/ResetPassword', [
'email' => $request->email,
'token' => $request->route('token'),
]));
Fortify::requestPasswordResetLinkView(fn (Request $request) => Inertia::render('auth/ForgotPassword', [
'status' => $request->session()->get('status'),
]));
Fortify::verifyEmailView(function (Request $request) {
$user = $request->user();
$expire = (int) Config::get('auth.verification.expire', 60);
$verificationUrl = null;
if ($user) {
$verificationUrl = URL::temporarySignedRoute(
'verification.verify', now()->addMinutes($expire), [
'id' => $user->getKey(),
'hash' => sha1($user->getEmailForVerification()),
]
);
}
$cacheKey = $user ? 'email_verify_code:'.$user->getKey() : null;
$hasCode = $cacheKey ? !is_null(Cache::get($cacheKey)) : false;
return Inertia::render('auth/VerifyEmail', [
'status' => $request->session()->get('status'),
'verificationUrl' => $verificationUrl,
'hasCode' => $hasCode,
]);
});
Fortify::registerView(fn () => Inertia::render('auth/Register'));
Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/TwoFactorChallenge'));
Fortify::confirmPasswordView(fn () => Inertia::render('auth/ConfirmPassword'));
}
private function configureRateLimiting(): void
{
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
RateLimiter::for('login', function (Request $request) {
$username = (string) $request->input(Fortify::username());
$throttleKey = Str::lower($username).'|'.$request->ip();
return Limit::perMinute(5)->by($throttleKey);
});
}
}