189 lines
7.6 KiB
PHP
189 lines
7.6 KiB
PHP
<?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);
|
|
});
|
|
}
|
|
}
|