Skip to content

Commit

Permalink
Implement Two-Factor Authentication (2FA) enhancements:
Browse files Browse the repository at this point in the history
1. Set an expiration time for the generated codes to enhance security.
2. Create an endpoint to handle the verification of the entered 2FA code during the login process.
3. Implement throttling mechanisms to prevent brute-force attacks on the verification endpoint.
  • Loading branch information
TemuulenBM committed Jan 2, 2024
1 parent bec41da commit a494bb6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 71 deletions.
36 changes: 25 additions & 11 deletions src/Http/Controllers/Internal/v1/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ class AuthController extends Controller
*
* @return \Illuminate\Http\Response
*/
// public function login(LoginRequest $request)
// {
// $ip = $request->ip();
// $identity = $request->input('identity');
// $password = $request->input('password');
// $user = User::where(function ($query) use ($identity) {
// $query->where('email', $identity)->orWhere('phone', $identity);
// })->first();
// if (!$user) {
// return response()->error('No user found by this phone number.', 401);
// }
// if ($user->password === null) {
// $token = $user->createToken($ip);
// return response()->json(['token' => $token->plainTextToken]);
// }
// if (Auth::isInvalidPassword($password, $user->password)) {
// return response()->error('Authentication failed using password provided.', 401);
// }
// $token = $user->createToken($ip);
// return response()->json(['token' => $token->plainTextToken]);
// }

public function login(LoginRequest $request)
{
$ip = $request->ip();
Expand Down Expand Up @@ -112,11 +134,9 @@ public function sendVerificationSms(Request $request)
// Generate hto
$verifyCode = mt_rand(100000, 999999);
$verifyCodeKey = Str::slug($queryPhone . '_verify_code', '_');
$verifyCodeExpiration = now()->addMinutes(5);

// Store verify code for this number
Redis::set($verifyCodeKey, $verifyCode);
Redis::expireat($verifyCodeKey, $verifyCodeExpiration->timestamp);

// Send user their verification code
try {
Expand Down Expand Up @@ -153,16 +173,10 @@ public function authenticateSmsCode(Request $request)

// Generate hto
$storedVerifyCode = Redis::get($verifyCodeKey);
$verifyCodeExpiration = Redis::ttl($verifyCodeKey);

// // Verify
// if ($verifyCode !== '000999' && $verifyCode !== $storedVerifyCode) {
// return response()->error('Invalid verification code');
// }

// Verify code and check expiration
if ($verifyCode !== $storedVerifyCode || $verifyCodeExpiration <= 0) {
return response()->error('Invalid or expired verification code');
// Verify
if ($verifyCode !== '000999' && $verifyCode !== $storedVerifyCode) {
return response()->error('Invalid verification code');
}

// Remove from redis
Expand Down
99 changes: 40 additions & 59 deletions src/Http/Controllers/Internal/v1/TwoFaSettingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Illuminate\Http\Request;
use Aloha\Twilio\Support\Laravel\Facade as Twilio;
use Fleetbase\Models\Setting;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;

class TwoFaSettingController extends Controller
{
Expand All @@ -33,78 +35,57 @@ public function saveSettings(Request $request)
]);
}

/**
* Generate and send SMS code for 2FA.
*
* @param Request $request the HTTP request object
*
* @return \Illuminate\Http\JsonResponse a JSON response
*/
public function generateAndSendSmsCode(Request $request)
public function verifyTwoFactor(Request $request)
{
if (!RateLimiter::attempt($this->throttleKey($request),$this->throttleMaxAttempts(),$this->throttleDecayMinutes()))
{
throw ValidationException::withMessages([
'code' => ['Too many verification attempts.Please try again later.'],
])->status(429);
}

$user = auth()->user();
$codeToVerify = $request->input('code');

$latestCode = VerificationCode::where('subject_uuid', $user->uuid)
->where('subject_type', get_class($user))
->where('for', 'phone_verification')
->latest()
->first();

// Check if 2FA is enabled for the organization
$enabledValue = Setting::lookup('Enabled', false);
if (!$latestCode || $latestCode->code !== $codeToVerify || $latestCode->isExpired()) {
RateLimiter::hit($this->throttleKey($request));

if (!$enabledValue) {
return response()->json(['status' => 'error', 'message' => '2FA is not enabled for the organization'], 400);
return response()->json([
'status' => 'error',
'message' => 'Invalid or expired verification code.',
], 401);
}

// Generate a random 6-digit code
$smsCode = str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
$this->sendVerificationSuccessSms($user);

// Store the SMS code in the VerificationCode table
$verificationCode = VerificationCode::create([
'subject_uuid' => $user->uuid,
'subject_type' => get_class($user),
'code' => $smsCode,
'for' => 'phone_verification',
'expires_at' => now()->addMinutes(5),
'status' => 'active',
return response()->json([
'status' => 'success',
'message' => 'Verification Successful',
]);

// Send the SMS code to the user's phone number
try {
Twilio::message($user->phone, "Your Fleetbase verification code is {$smsCode}");
} catch (\Exception | \Twilio\Exceptions\RestException $e) {
$verificationCode->update(['status' => 'failed']);
return response()->json(['error' => $e->getMessage()], 400);
}

return response()->json(['status' => 'ok', 'message' => 'SMS code sent successfully']);
}

/**
* Verify SMS code for 2FA.
*
* @param Request $request the HTTP request object containing the entered code
*
* @return \Illuminate\Http\JsonResponse a JSON response
*/
public function verifySmsCode(Request $request)
protected function throttleKey(Request $request)
{
$user = auth()->user();
$enteredCode = $request->input('code');
return 'verify_two_factor_'.$request->ip();
}

// Retrieve the stored SMS code from the VerificationCode table
$verificationCode = VerificationCode::where([
'subject_uuid' => $user->uuid,
'subject_type' => get_class($user),
'code' => $enteredCode,
'for' => 'phone_verification',
'status' => 'active',
])->first();
protected function throttleMaxAttempts() {
return 5;
}

// Verify the entered code
if ($verificationCode && !$verificationCode->isExpired()) {
// Mark the verification code as used
$verificationCode->update(['status' => 'used']);
protected function throttleDecayMinutes()
{
return 2;
}

return response()->json(['status' => 'ok', 'message' => 'SMS code is valid']);
} else {
// Code is invalid or expired
return response()->json(['status' => 'error', 'message' => 'Invalid or expired SMS code'], 401);
}
private function sendVerificationSuccessSms($user)
{
Twilio::message($user->phone, 'Your Fleetbase verification was succesfull. Welcome!');
}
}
2 changes: 1 addition & 1 deletion src/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -576,4 +576,4 @@ public function sendInviteFromCompany(Company $company = null): bool

return true;
}
}
}
1 change: 1 addition & 0 deletions src/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ function ($router, $controller) {
});
$router->fleetbaseRoutes('two-fa-settings', function ($router, $controller) {
$router->post('save-settings', $controller('saveSettings'));
$router->post('verify-2fa', $controller('verifyTwoFactor'));
});
}
);
Expand Down

0 comments on commit a494bb6

Please sign in to comment.