This commit is contained in:
TechNoNerd87 2025-11-26 16:33:20 -08:00 committed by GitHub
commit b75c183c2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 126 additions and 8 deletions

View File

@ -131,9 +131,28 @@ class LoginController extends BaseController
//2FA //2FA
if ($user->google_2fa_secret && $request->has('one_time_password')) { if ($user->google_2fa_secret && $request->has('one_time_password')) {
$google2fa = new Google2FA(); $otp = $request->input('one_time_password');
$secret = decrypt($user->google_2fa_secret);
$timestamp = false;
if (strlen($request->input('one_time_password')) == 0 || !$google2fa->verifyKey(decrypt($user->google_2fa_secret), $request->input('one_time_password'))) { if (strlen($otp) > 0) {
// Try SHA512 first (new algorithm) with timestamp to prevent OTP reuse
$google2fa = new Google2FA();
$google2fa->setAlgorithm(\PragmaRX\Google2FA\Support\Constants::SHA512);
$timestamp = $google2fa->verifyKeyNewer($secret, $otp, $user->google_2fa_ts ?? 0);
// Fall back to SHA1 for existing users (backward compatibility)
if ($timestamp === false) {
$google2fa = new Google2FA();
$timestamp = $google2fa->verifyKeyNewer($secret, $otp, $user->google_2fa_ts ?? 0);
}
}
if ($timestamp !== false) {
// Update timestamp to prevent OTP reuse
$user->google_2fa_ts = $timestamp;
$user->save();
} else {
return response() return response()
->json(['message' => ctrans('texts.invalid_one_time_password')], 422) ->json(['message' => ctrans('texts.invalid_one_time_password')], 422)
->header('X-App-Version', config('ninja.app_version')) ->header('X-App-Version', config('ninja.app_version'))

View File

@ -40,7 +40,8 @@ class TwoFactorController extends BaseController
} }
$google2fa = new Google2FA(); $google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey(); $google2fa->setAlgorithm(\PragmaRX\Google2FA\Support\Constants::SHA512);
$secret = $google2fa->generateSecretKey(32);
$qr_code = $google2fa->getQRCodeUrl( $qr_code = $google2fa->getQRCodeUrl(
config('ninja.app_name'), config('ninja.app_name'),
@ -48,6 +49,9 @@ class TwoFactorController extends BaseController
$secret $secret
); );
// Append algorithm parameter for SHA512
$qr_code .= '&algorithm=SHA512';
$data = [ $data = [
'secret' => $secret, 'secret' => $secret,
'qrCode' => $qr_code, 'qrCode' => $qr_code,
@ -58,20 +62,30 @@ class TwoFactorController extends BaseController
public function enableTwoFactor(EnableTwoFactorRequest $request) public function enableTwoFactor(EnableTwoFactorRequest $request)
{ {
$google2fa = new Google2FA();
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
$secret = $request->input('secret'); $secret = $request->input('secret');
$oneTimePassword = $request->input('one_time_password'); $oneTimePassword = $request->input('one_time_password');
if ($google2fa->verifyKey($secret, $oneTimePassword) && $user->phone && $user->email_verified_at) { // Try SHA512 first (new algorithm)
$google2fa = new Google2FA();
$google2fa->setAlgorithm(\PragmaRX\Google2FA\Support\Constants::SHA512);
$timestamp = $google2fa->verifyKeyNewer($secret, $oneTimePassword, 0);
// Fall back to SHA1 for manual entry (authenticator apps default to SHA1)
if ($timestamp === false) {
$google2fa = new Google2FA();
$timestamp = $google2fa->verifyKeyNewer($secret, $oneTimePassword, 0);
}
if ($timestamp !== false && $user->phone && $user->email_verified_at) {
$user->google_2fa_secret = encrypt($secret); $user->google_2fa_secret = encrypt($secret);
$user->google_2fa_ts = $timestamp;
$user->save(); $user->save();
return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200); return response()->json(['message' => ctrans('texts.enabled_two_factor')], 200);
} elseif (! $secret || ! $google2fa->verifyKey($secret, $oneTimePassword)) { } elseif (! $secret || $timestamp === false) {
return response()->json(['message' => ctrans('texts.invalid_one_time_password')], 400); return response()->json(['message' => ctrans('texts.invalid_one_time_password')], 400);
} elseif (! $user->phone) { } elseif (! $user->phone) {
return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400); return response()->json(['message' => ctrans('texts.set_phone_for_two_factor')], 400);
@ -94,6 +108,7 @@ class TwoFactorController extends BaseController
$user = auth()->user(); $user = auth()->user();
$user->google_2fa_secret = null; $user->google_2fa_secret = null;
$user->google_2fa_ts = null;
$user->save(); $user->save();
return $this->itemResponse($user); return $this->itemResponse($user);

View File

@ -175,6 +175,7 @@ class User extends Authenticatable implements MustVerifyEmail
'remember_token', 'remember_token',
'google_2fa_secret', 'google_2fa_secret',
'google_2fa_phone', 'google_2fa_phone',
'google_2fa_ts',
'remember_2fa_token', 'remember_2fa_token',
'slack_webhook_url', 'slack_webhook_url',
'referral_earnings', 'referral_earnings',

View File

@ -0,0 +1,83 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->integer('google_2fa_ts')->nullable()->after('google_2fa_secret');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('google_2fa_ts');
});
}
};