diff --git a/src/Http/Controllers/Internal/v1/AuthController.php b/src/Http/Controllers/Internal/v1/AuthController.php index 0703b4b..213c289 100644 --- a/src/Http/Controllers/Internal/v1/AuthController.php +++ b/src/Http/Controllers/Internal/v1/AuthController.php @@ -17,6 +17,7 @@ use Fleetbase\Models\VerificationCode; use Fleetbase\Notifications\UserForgotPassword; use Fleetbase\Support\Auth; +use Fleetbase\Support\TwoFactorAuth; use Fleetbase\Support\Utils; use Illuminate\Http\Request; use Illuminate\Support\Carbon; @@ -30,48 +31,58 @@ 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(); - $email = $request->input('email'); + $identity = $request->input('identity'); $password = $request->input('password'); - $user = User::where('email', $email)->first(); + + $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 email.', 401); + return response()->error('No user found by this phone number.', 401); } + $token = $user->createToken($ip); + + // Check if 2FA enabled + if (TwoFactorAuth::isEnabled()) { + $twoFaSession = TwoFactorAuth::start(); + return response()->json(['two_fa_session' => $twoFaSession]); + } + + 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(); + // $email = $request->input('email'); + // $password = $request->input('password'); + // $user = User::where('email', $email)->first(); + + // if (!$user) { + // return response()->error('No user found by this email.', 401); + // } + + // 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]); + // } + /** * Takes a request username/ or email and password and attempts to authenticate user * will return the user model if the authentication was successful, else will 400. diff --git a/src/Http/Controllers/Internal/v1/TwoFaController.php b/src/Http/Controllers/Internal/v1/TwoFaController.php new file mode 100644 index 0000000..f5e26c1 --- /dev/null +++ b/src/Http/Controllers/Internal/v1/TwoFaController.php @@ -0,0 +1,64 @@ +twoFactorAuth = $twoFactorAuth; + } + + /** + * Save Two-Factor Authentication settings. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function saveSettings(Request $request) + { + return TwoFactorAuth::saveSettings($request); + } + + /** + * Get Two-Factor Authentication settings. + * + * @return \Illuminate\Http\JsonResponse + */ + public function getSettings() + { + return TwoFactorAuth::getSettings(); + } + + /** + * Verify Two-Factor Authentication code. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function verifyTwoFactor(Request $request) + { + return TwoFactorAuth::verifyTwoFactor($request); + } +} diff --git a/src/Http/Controllers/Internal/v1/TwoFaSettingController.php b/src/Http/Controllers/Internal/v1/TwoFaSettingController.php deleted file mode 100644 index 7d024b7..0000000 --- a/src/Http/Controllers/Internal/v1/TwoFaSettingController.php +++ /dev/null @@ -1,91 +0,0 @@ -input('twoFaSettings'); - if (!is_array($twoFaSettings)) { - throw new \Exception('Invalid 2FA settings data.'); - } - Setting::configure('2fa', $twoFaSettings); - - return response()->json([ - 'status' => 'ok', - 'message' => '2Fa settings succesfully saved.', - ]); - } - - 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(); - - if (!$latestCode || $latestCode->code !== $codeToVerify || $latestCode->isExpired()) { - RateLimiter::hit($this->throttleKey($request)); - - return response()->json([ - 'status' => 'error', - 'message' => 'Invalid or expired verification code.', - ], 401); - } - - $this->sendVerificationSuccessSms($user); - - return response()->json([ - 'status' => 'success', - 'message' => 'Verification Successful', - ]); - } - - protected function throttleKey(Request $request) - { - return 'verify_two_factor_'.$request->ip(); - } - - protected function throttleMaxAttempts() { - return 5; - } - - protected function throttleDecayMinutes() - { - return 2; - } - - private function sendVerificationSuccessSms($user) - { - Twilio::message($user->phone, 'Your Fleetbase verification was succesfull. Welcome!'); - } -} diff --git a/src/Support/TwoFactorAuth.php b/src/Support/TwoFactorAuth.php new file mode 100644 index 0000000..eb5726e --- /dev/null +++ b/src/Support/TwoFactorAuth.php @@ -0,0 +1,141 @@ +input('twoFaSettings'); + if (!is_array($twoFaSettings)) { + throw new \Exception('Invalid 2FA settings data.'); + } + Setting::configure('2fa', $twoFaSettings); + + return response()->json([ + 'status' => 'ok', + 'message' => '2Fa settings successfully saved.', + ]); + } + + /** + * Get Two-Factor Authentication settings. + * + * @return \Illuminate\Http\JsonResponse + */ + public static function getSettings() + { + $twoFaSettings = Setting::lookup('2fa', ['enabled' => false, 'method' => 'authenticator_app']); + + return response()->json($twoFaSettings); + } + + /** + * Verify Two-Factor Authentication code. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public static function verifyTwoFactor($request) + { + if (!RateLimiter::attempt(self::throttleKey($request), self::throttleMaxAttempts(), self::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(); + + if (!$latestCode || $latestCode->code !== $codeToVerify || $latestCode->isExpired()) { + RateLimiter::hit(self::throttleKey($request)); + + return response()->json([ + 'status' => 'error', + 'message' => 'Invalid or expired verification code.', + ], 401); + } + + self::sendVerificationSuccessSms($user); + + return response()->json([ + 'status' => 'success', + 'message' => 'Verification Successful', + ]); + } + + /** + * Get the throttle key based on the request's IP. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected static function throttleKey($request) + { + return 'verify_two_factor_' . $request->ip(); + } + + /** + * Get the maximum number of attempts allowed in the throttle. + * + * @return int + */ + protected static function throttleMaxAttempts() + { + return 5; + } + + /** + * Get the decay time in minutes for the throttle. + * + * @return int + */ + protected static function throttleDecayMinutes() + { + return 2; + } + + /** + * Send a success SMS after successful verification. + * + * @param mixed $user + */ + private static function sendVerificationSuccessSms($user) + { + Twilio::message($user->phone, 'Your Fleetbase verification was successful. Welcome!'); + } + + public static function isEnabled() + { + return Setting::lookup('2fa', ['enabled']); + } + + public static function start() + { + return true; + } +} diff --git a/src/routes.php b/src/routes.php index 656265e..d29b858 100644 --- a/src/routes.php +++ b/src/routes.php @@ -118,6 +118,13 @@ function ($router, $controller) { $router->post('test-notification-channels-config', $controller('testNotificationChannelsConfig')); } ); + $router->fleetbaseRoutes( + 'two-fa', + function ($router, $controller) { + $router->post('settings', $controller('saveSettings')); + $router->get('settings', $controller('getSettings')); + } + ); $router->fleetbaseRoutes('api-events'); $router->fleetbaseRoutes('api-request-logs'); $router->fleetbaseRoutes( @@ -167,10 +174,6 @@ function ($router, $controller) { $router->delete('bulk-delete', $controller('bulkDelete')); $router->post('save-settings', $controller('saveSettings')); }); - $router->fleetbaseRoutes('two-fa-settings', function ($router, $controller) { - $router->post('save-settings', $controller('saveSettings')); - $router->post('verify-2fa', $controller('verifyTwoFactor')); - }); } ); }