validate([ 'email' => ['required', 'email'], 'password' => ['string'], ]); $key = 'operator-login:'.mb_strtolower($this->email).'|'.request()->ip(); if (RateLimiter::tooManyAttempts($key, 5)) { throw ValidationException::withMessages([ 'email' => __('auth.throttle', ['seconds' => RateLimiter::availableIn($key)]), ]); } $operator = Operator::where('email', $this->email)->first(); // One message for every failure — a different message for "no such // account" tells a stranger which addresses are operators. if ($operator === null || ! Hash::check($this->password, $operator->password) || ! $operator->isActive() || ! $operator->isOperator()) { RateLimiter::hit($key, 60); $this->reset('password'); throw ValidationException::withMessages(['email' => __('auth.failed')]); } RateLimiter::clear($key); if ($operator->two_factor_confirmed_at !== null) { // Not signed in yet. The id is parked in the session and the guard // stays untouched until the code checks out. session(['operator.2fa.id' => $operator->id, 'operator.2fa.remember' => $this->remember]); $this->redirectRoute('admin.two-factor', navigate: false); return; } $this->completeLoginAndRedirect($operator, $this->remember); } /** * Shared with the two-factor challenge, so both exits behave identically. * * Re-checks isActive()/isOperator() here, not only at the password step: * an operator can be disabled, or lose every console role, in the window * between entering a password and completing a PENDING two-factor * challenge — an owner revoking access while someone is mid-login is the * realistic case, not a contrived one, and the challenge on its own only * ever confirmed the row still existed. One check, in the one place both * entry points already share, rather than a second copy in the challenge * that the next change forgets to keep in sync. * * @return bool False when the operator no longer qualifies: the login is * refused, not completed — neither the guard nor the * session is touched, and the caller decides how to say so. */ public static function completeLogin(Operator $operator, bool $remember): bool { if (! $operator->isActive() || ! $operator->isOperator()) { return false; } Auth::guard('operator')->login($operator, $remember); session()->regenerate(); $operator->forceFill(['last_login_at' => now()])->save(); return true; } private function completeLoginAndRedirect(Operator $operator, bool $remember): void { if (! self::completeLogin($operator, $remember)) { throw ValidationException::withMessages(['email' => __('auth.failed')]); } $this->redirect(AdminArea::home(), navigate: false); } public function render() { return view('livewire.auth.operator-login'); } }