feat: implement premium Email 2FA authentication integrated with auth flow
This commit is contained in:
@@ -38,6 +38,29 @@ class AuthenticatedSessionController extends Controller
|
||||
// If user has 2FA enabled, redirect to challenge screen
|
||||
if ($user->two_factor_confirmed_at && $user->two_factor_secret) {
|
||||
$request->session()->put('two_factor_user_id', $user->id);
|
||||
$request->session()->put('two_factor_type', 'totp');
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->forget('password_hash_web');
|
||||
|
||||
return redirect()->route('two-factor.challenge');
|
||||
}
|
||||
|
||||
// If user has Email 2FA enabled, redirect to email challenge
|
||||
if ($user->email_2fa_enabled) {
|
||||
$code = str_pad(mt_rand(100000, 999999), 6, '0', STR_PAD_LEFT);
|
||||
$user->update([
|
||||
'email_2fa_code' => $code,
|
||||
'email_2fa_expires_at' => now()->addMinutes(10),
|
||||
]);
|
||||
|
||||
try {
|
||||
\Illuminate\Support\Facades\Mail::to($user->email)->send(new \App\Mail\Send2FACode($code));
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error("Failed to send 2FA Email Code: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$request->session()->put('two_factor_user_id', $user->id);
|
||||
$request->session()->put('two_factor_type', 'email');
|
||||
Auth::guard('web')->logout();
|
||||
$request->session()->forget('password_hash_web');
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class SettingsController extends Controller
|
||||
'enabled' => $twoFactorEnabled,
|
||||
'qr_code' => $qrCode,
|
||||
'secret' => $secret,
|
||||
'email_enabled' => (bool)$user->email_2fa_enabled,
|
||||
'recovery_codes' => $user->two_factor_recovery_codes
|
||||
? json_decode($user->two_factor_recovery_codes, true)
|
||||
: [],
|
||||
|
||||
@@ -103,6 +103,26 @@ class TwoFactorController extends Controller
|
||||
return back()->with('success', 'Two-Factor Authentication has been disabled.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable Email 2FA.
|
||||
*/
|
||||
public function toggleEmail(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'password' => 'required|current_password',
|
||||
'enabled' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$user = auth()->user();
|
||||
$user->update([
|
||||
'email_2fa_enabled' => $request->enabled,
|
||||
]);
|
||||
|
||||
$status = $request->enabled ? 'enabled' : 'disabled';
|
||||
return back()->with('success', "Two-Factor Authentication via Email has been {$status} successfully.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Regenerate recovery codes.
|
||||
*/
|
||||
@@ -126,7 +146,11 @@ class TwoFactorController extends Controller
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
return Inertia::render('TwoFactor/Challenge');
|
||||
$type = $request->session()->get('two_factor_type', 'totp');
|
||||
|
||||
return Inertia::render('TwoFactor/Challenge', [
|
||||
'type' => $type,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,6 +163,7 @@ class TwoFactorController extends Controller
|
||||
]);
|
||||
|
||||
$userId = $request->session()->get('two_factor_user_id');
|
||||
$type = $request->session()->get('two_factor_type', 'totp');
|
||||
|
||||
if (!$userId) {
|
||||
return redirect()->route('login');
|
||||
@@ -146,32 +171,86 @@ class TwoFactorController extends Controller
|
||||
|
||||
$user = \App\Models\User::find($userId);
|
||||
|
||||
if (!$user || !$user->two_factor_secret) {
|
||||
$request->session()->forget('two_factor_user_id');
|
||||
if (!$user) {
|
||||
$request->session()->forget(['two_factor_user_id', 'two_factor_type']);
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$code = $request->code;
|
||||
$valid = $this->google2fa->verifyKey($user->two_factor_secret, $code);
|
||||
if ($type === 'email') {
|
||||
if (!$user->email_2fa_enabled || !$user->email_2fa_code) {
|
||||
$request->session()->forget(['two_factor_user_id', 'two_factor_type']);
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
$recoveryCodes = json_decode($user->two_factor_recovery_codes ?? '[]', true);
|
||||
if (in_array($code, $recoveryCodes)) {
|
||||
$remaining = array_filter($recoveryCodes, fn($c) => $c !== $code);
|
||||
$user->update(['two_factor_recovery_codes' => json_encode(array_values($remaining))]);
|
||||
$valid = true;
|
||||
if ($user->email_2fa_code !== $request->code || !$user->email_2fa_expires_at || $user->email_2fa_expires_at->isPast()) {
|
||||
return back()->withErrors(['code' => 'Invalid or expired authentication code. Please try again.']);
|
||||
}
|
||||
|
||||
// Code is valid! Clear it
|
||||
$user->update([
|
||||
'email_2fa_code' => null,
|
||||
'email_2fa_expires_at' => null,
|
||||
]);
|
||||
} else {
|
||||
if (!$user->two_factor_secret) {
|
||||
$request->session()->forget(['two_factor_user_id', 'two_factor_type']);
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$code = $request->code;
|
||||
$valid = $this->google2fa->verifyKey($user->two_factor_secret, $code);
|
||||
|
||||
if (!$valid) {
|
||||
$recoveryCodes = json_decode($user->two_factor_recovery_codes ?? '[]', true);
|
||||
if (in_array($code, $recoveryCodes)) {
|
||||
$remaining = array_filter($recoveryCodes, fn($c) => $c !== $code);
|
||||
$user->update(['two_factor_recovery_codes' => json_encode(array_values($remaining))]);
|
||||
$valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
return back()->withErrors(['code' => 'Invalid code. Please try again.']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
return back()->withErrors(['code' => 'Invalid code. Please try again.']);
|
||||
}
|
||||
|
||||
$request->session()->forget('two_factor_user_id');
|
||||
$request->session()->forget(['two_factor_user_id', 'two_factor_type']);
|
||||
|
||||
\Illuminate\Support\Facades\Auth::login($user);
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend Email 2FA verification code.
|
||||
*/
|
||||
public function resendCode(Request $request)
|
||||
{
|
||||
if (!$request->session()->has('two_factor_user_id') || $request->session()->get('two_factor_type') !== 'email') {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$userId = $request->session()->get('two_factor_user_id');
|
||||
$user = \App\Models\User::find($userId);
|
||||
|
||||
if (!$user || !$user->email_2fa_enabled) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$code = str_pad(mt_rand(100000, 999999), 6, '0', STR_PAD_LEFT);
|
||||
$user->update([
|
||||
'email_2fa_code' => $code,
|
||||
'email_2fa_expires_at' => now()->addMinutes(10),
|
||||
]);
|
||||
|
||||
try {
|
||||
\Illuminate\Support\Facades\Mail::to($user->email)->send(new \App\Mail\Send2FACode($code));
|
||||
} catch (\Exception $e) {
|
||||
\Illuminate\Support\Facades\Log::error("Failed to resend 2FA Email Code: " . $e->getMessage());
|
||||
return back()->withErrors(['code' => 'Failed to send email. Please check SMTP configuration or try again.']);
|
||||
}
|
||||
|
||||
return back()->with('success', 'A new verification code has been sent to your email.');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user