Files
timebank-cc-public/app/Providers/FortifyServiceProvider.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

148 lines
6.1 KiB
PHP

<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\ValidationException;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Http\Controllers\VerifyEmailController;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
// Disable Fortify route registration, a selection of the initial fortify routes are moved ot /routes/fortify.php.
// Custom routes with the same name however are registered in web.php. This is done because Fortify does not allow to register routes with the same name.
Fortify::ignoreRoutes();
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::verifyEmailView(fn () => view('auth.verify-email'));
RateLimiter::for('login', function (Request $request) {
$loginIdentifier = (string) $request->input('name'); // Changed from $request->email to $request->input('name')
return Limit::perMinute(10)->by($loginIdentifier.$request->ip());
});
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
// Rehash Cyclos salted sha256 passwords on first login
Fortify::authenticateUsing(function (Request $request) {
// Attempt to find the user by email or name.
$user = User::where('email', $request->name)
->orWhere('name', $request->name)
->first();
if (!$user) {
info('Auth failed: User not identified with login identifier: ' . $request->auth);
return null;
}
if ($user->isRemoved()) {
info('Auth failed: User ' . $user->name . ' is marked as removed (soft-deleted).');
return null;
}
// If maintenance mode is enabled, check if user has admin relationship
if (isMaintenanceMode()) {
$hasAdminRelation = $user->admins()->exists();
if (!$hasAdminRelation) {
info('Auth failed: Maintenance mode is active and user ' . $user->name . ' does not have admin relationship.');
session()->flash('maintenance_mode_active', true);
throw ValidationException::withMessages([
'name' => __('Login is currently disabled due to site maintenance. Only administrator accounts can access the site at this time. Please try again later.'),
]);
}
info('Auth allowed during maintenance: User ' . $user->name . ' has admin relationship.');
}
info('User identified: ' . $user->name . ' (ID: ' . $user->id . ') attempting login with identifier: ' . $request->auth);
$passwordVerified = false;
// Check for Cyclos password first if salt exists
if (!empty($user->cyclos_salt)) {
info('Attempting Cyclos password verification for user: ' . $user->name);
$concatenated = $user->cyclos_salt . $request->password;
$hashedInputPassword = hash("sha256", $concatenated);
if (strtolower($hashedInputPassword) === strtolower($user->password)) {
info('Cyclos password verified for user: ' . $user->name);
// Prepare user model for update: rehash password and clear salt
$user->password = Hash::make($request->password);
$user->cyclos_salt = null;
$passwordVerified = true;
info('Cyclos password prepared for rehashing for user: ' . $user->name);
} else {
info('Cyclos password verification failed for user: ' . $user->name . '. Will attempt standard Laravel hash check.');
}
}
// If not verified by Cyclos, or if cyclos_salt was empty, try Laravel's default hash check
if (!$passwordVerified && Hash::check($request->password, $user->password)) {
info('Laravel password verified for user: ' . $user->name);
$passwordVerified = true;
}
if ($passwordVerified) {
info('Auth success for user: ' . $user->name);
// Update last login details
$now = Carbon::now()->toDateTimeString();
$user->last_login_at = $now;
$user->last_login_ip = $request->getClientIp();
if (timebank_config('profile_inactive.re-activate_at_login')) {
if (!$user->isActive()) {
$user->inactive_at = null;
$activated = true;
}
}
// Save all changes (rehashed password, cleared salt, login details)
$user->save();
if (isset($activated) && $activated) {
info('User re-activated: ' . $user->name);
}
return $user;
}
info('Auth failed: Password mismatch for user: ' . $user->name . ' with identifier: ' . $request->auth);
return null;
});
}
}