mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-30 02:24:30 -05:00
Add current_team_id to users so the last active team is restored on login instead of always defaulting to the personal team. When a user belongs to multiple teams and has no valid stored choice, redirect them to a new team.select screen (SelectTeam Livewire component) to pick one, rather than silently choosing the first team. Update Fortify and OAuth login flows to use the new resolveStoredTeam() logic.
172 lines
6.5 KiB
PHP
172 lines
6.5 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\OauthSetting;
|
|
use App\Models\TeamInvitation;
|
|
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 Laravel\Fortify\Contracts\RegisterResponse;
|
|
use Laravel\Fortify\Fortify;
|
|
|
|
class FortifyServiceProvider extends ServiceProvider
|
|
{
|
|
/**
|
|
* Register any application services.
|
|
*/
|
|
public function register(): void
|
|
{
|
|
$this->app->instance(RegisterResponse::class, new class implements RegisterResponse
|
|
{
|
|
public function toResponse($request)
|
|
{
|
|
// First user (root) will be redirected to /settings instead of / on registration.
|
|
if ($request->user()->currentTeam->id === 0) {
|
|
return redirect()->route('settings.index');
|
|
}
|
|
|
|
return redirect(RouteServiceProvider::HOME);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Bootstrap any application services.
|
|
*/
|
|
public function boot(): void
|
|
{
|
|
Fortify::createUsersUsing(CreateNewUser::class);
|
|
Fortify::registerView(function () {
|
|
$isFirstUser = User::count() === 0;
|
|
|
|
$settings = instanceSettings();
|
|
if (! $settings->isPasswordRegistrationAllowed()) {
|
|
return redirect()->route('login');
|
|
}
|
|
|
|
return view('auth.register', [
|
|
'isFirstUser' => $isFirstUser,
|
|
]);
|
|
});
|
|
|
|
Fortify::loginView(function () {
|
|
$settings = instanceSettings();
|
|
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
|
|
$users = User::count();
|
|
if ($users == 0 && $settings->isPasswordRegistrationAllowed()) {
|
|
// If there are no users and password registration is allowed, redirect to registration.
|
|
return redirect()->route('register');
|
|
}
|
|
|
|
return view('auth.login', [
|
|
'is_registration_enabled' => $settings->isPasswordRegistrationAllowed(),
|
|
'enabled_oauth_providers' => $enabled_oauth_providers,
|
|
]);
|
|
});
|
|
|
|
Fortify::authenticateUsing(function (Request $request) {
|
|
$email = strtolower($request->email);
|
|
$user = User::where('email', $email)->with('teams')->first();
|
|
if (
|
|
$user &&
|
|
Hash::check($request->password, $user->password)
|
|
) {
|
|
$user->updated_at = now();
|
|
$user->save();
|
|
|
|
// Check if user has a pending invitation they haven't accepted yet
|
|
$invitation = TeamInvitation::whereEmail($email)->first();
|
|
if ($invitation && $invitation->isValid()) {
|
|
// User is logging in for the first time after being invited
|
|
// Attach them to the invited team if not already attached
|
|
if (! $user->teams()->where('team_id', $invitation->team->id)->exists()) {
|
|
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
|
}
|
|
$user->currentTeam = $invitation->team;
|
|
$invitation->delete();
|
|
session(['currentTeam' => $user->currentTeam]);
|
|
} else {
|
|
// Restore the last active team; only fall back when unambiguous.
|
|
$team = $user->resolveStoredTeam();
|
|
if (! $team && $user->teams->isEmpty()) {
|
|
$team = $user->recreate_personal_team();
|
|
}
|
|
if ($team) {
|
|
session(['currentTeam' => $user->currentTeam = $team]);
|
|
}
|
|
// Otherwise (multiple teams, no stored choice) leave the session
|
|
// team unset so the user is sent to the team-selection screen.
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
});
|
|
Fortify::requestPasswordResetLinkView(function () {
|
|
return view('auth.forgot-password');
|
|
});
|
|
Fortify::resetPasswordView(function ($request) {
|
|
return view('auth.reset-password', ['request' => $request]);
|
|
});
|
|
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
|
|
|
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
|
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
|
|
|
Fortify::confirmPasswordView(function () {
|
|
return view('auth.confirm-password');
|
|
});
|
|
|
|
Fortify::twoFactorChallengeView(function () {
|
|
return view('auth.two-factor-challenge');
|
|
});
|
|
|
|
RateLimiter::for('force-password-reset', function (Request $request) {
|
|
return Limit::perMinute(15)->by($request->user()->id);
|
|
});
|
|
|
|
RateLimiter::for('forgot-password', function (Request $request) {
|
|
// Use real client IP (not spoofable forwarded headers)
|
|
$realIp = $request->server('REMOTE_ADDR') ?? $request->ip();
|
|
|
|
$limits = [
|
|
Limit::perMinutes(10, 3)->by('forgot-password:ip:'.sha1($realIp)),
|
|
];
|
|
|
|
$emailIdentity = normalize_email_identity($request->input('email'));
|
|
if ($emailIdentity !== null) {
|
|
$limits[] = Limit::perHour(3)->by('forgot-password:email-identity:'.sha1($emailIdentity));
|
|
}
|
|
|
|
return $limits;
|
|
});
|
|
|
|
RateLimiter::for('login', function (Request $request) {
|
|
$email = (string) $request->email;
|
|
// Use email + real client IP (not spoofable forwarded headers)
|
|
// server('REMOTE_ADDR') gives the actual connecting IP before proxy headers
|
|
$realIp = $request->server('REMOTE_ADDR') ?? $request->ip();
|
|
|
|
return Limit::perMinute(5)->by($email.'|'.$realIp);
|
|
});
|
|
|
|
RateLimiter::for('magic-link', function (Request $request) {
|
|
$realIp = $request->server('REMOTE_ADDR') ?? $request->ip();
|
|
$token = (string) $request->input('token');
|
|
|
|
return Limit::perMinute(5)->by(hash('sha256', $token.'|'.$realIp));
|
|
});
|
|
|
|
RateLimiter::for('two-factor', function (Request $request) {
|
|
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
|
});
|
|
}
|
|
}
|