Created
March 12, 2024 04:45
-
-
Save erdum/7d050a60066ad285afc3ac47e81a0f16 to your computer and use it in GitHub Desktop.
Authentication Service Class for Laravel REST API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
use Kreait\Firebase\Factory; | |
use App\Jobs\ProcessEmail; | |
use App\Exceptions\InvalidIdTokenException; | |
use App\Exceptions\UserAlreadyRegisteredException; | |
use App\Exceptions\UserBlockedException; | |
use App\Exceptions\AccessForbiddenException; | |
use App\Exceptions\InvalidCredentialsException; | |
use App\Exceptions\PasswordNotSetException; | |
use App\Exceptions\UnverifiedEmailException; | |
use App\Exceptions\IncorrectOtpException; | |
use App\Exceptions\OtpExpiredException; | |
use App\Exceptions\UserNotFoundException; | |
use App\Models\User; | |
class FirebaseAuthService | |
{ | |
protected $auth; | |
public function __construct() | |
{ | |
$firebase = (new Factory)->withServiceAccount( | |
base_path() | |
. DIRECTORY_SEPARATOR | |
. config('firebase.projects.app.credentials') | |
); | |
$this->auth = $firebase->createAuth(); | |
} | |
public function social_register($id_token, $is_business) | |
{ | |
try { | |
$verified_id_token = $this->auth->verifyIdToken($id_token); | |
} catch (\Kreait\Firebase\Exception\Auth\FailedToVerifyToken $error) { | |
throw new InvalidIdTokenException(); | |
} | |
$trying_uid = $verified_id_token->claims()->get('sub'); | |
$trying_user = $this->auth->getUser($trying_uid); | |
if (is_user_already_registered($trying_user->email)) { | |
throw new UserAlreadyRegisteredException(); | |
} | |
$user = User::updateOrCreate( | |
['email' => $trying_user->email], | |
[ | |
'name' => $trying_user->displayName, | |
'uid' => $trying_user->uid, | |
'email_verified_at' => now(), | |
'balance' => 0 | |
] | |
); | |
if ($is_business) { | |
$user->type = 'store_owner'; | |
$user->save(); | |
} | |
return ['message' => 'User has been successfully registered']; | |
} | |
public function social_login($id_token) | |
{ | |
try { | |
$verified_id_token = $this->auth->verifyIdToken($id_token); | |
} catch (\Kreait\Firebase\Exception\Auth\FailedToVerifyToken $error) { | |
throw new InvalidIdTokenException(); | |
} | |
$trying_uid = $verified_id_token->claims()->get('sub'); | |
$trying_user = $this->auth->getUser($trying_uid); | |
// if ($trying_user->password != null) { | |
// throw new \Exception( | |
// 'User has created the account with email & password', | |
// 400 | |
// ); | |
// } | |
if (empty(User::where('email', $trying_user->email)->first())) { | |
throw new UserNotFoundException(); | |
} | |
$user = User::where('email', $trying_user->email) | |
->where('uid', $trying_user->uid)->first(); | |
if (empty($user)) { | |
throw new UserNotFoundException(); | |
} | |
if ($user?->is_blocked) { | |
throw new UserBlockedException(); | |
} | |
if ($user?->type == 'admin') { | |
throw new AccessForbiddenException(); | |
} | |
$user->uid = $trying_uid; | |
$user->save(); | |
// $user->tokens()->delete(); | |
return ['token' => $this->generate_token($user)]; | |
} | |
public function email_register($full_name, $email, $password, $is_business) | |
{ | |
if ($this->is_user_already_registered($email)) { | |
throw new UserAlreadyRegisteredException(); | |
} | |
$uid = null; | |
try { | |
$created_user = $this->auth->createUser([ | |
'displayName' => $full_name, | |
'email' => $email, | |
'password' => $password, | |
'emailVerified' => false | |
]); | |
$uid = $created_user->uid; | |
} catch (\Kreait\Firebase\Exception\Auth\EmailExists $error) { | |
$trying_user = $this->auth->getUserByEmail($email); | |
$updated_user = $this->auth->updateUser($trying_user->uid, [ | |
'displayName' => $full_name, | |
'password' => $password | |
]); | |
$uid = $updated_user->uid; | |
} | |
$user = User::updateOrCreate( | |
['email' => $email], | |
[ | |
'name' => $full_name, | |
'password' => hash('sha256', $password), | |
'uid' => $uid, | |
'email_verified_at' => null, | |
'balance' => 0 | |
] | |
); | |
if ($is_business) { | |
$user->type = 'store_owner'; | |
$user->save(); | |
} | |
$this->send_otp($email, true); | |
return [ | |
'message' => 'OTP has been successfully sent to the user email' | |
]; | |
} | |
public function email_login($email, $password) | |
{ | |
$user = User::where('email', $email)->first(); | |
if ($user?->is_blocked) { | |
throw new UserBlockedException(); | |
} | |
if ($user?->type == 'admin') { | |
throw new AccessForbiddenException(); | |
} | |
if (empty($user)) { | |
throw new InvalidCredentialsException(); | |
} | |
if ($user->email_verified_at == null) { | |
throw new UnverifiedEmailException(); | |
} | |
if ($user->password == null) { | |
throw new PasswordNotSetException(); | |
} | |
if (!hash_equals(hash('sha256', $password), $user->password)) { | |
throw new InvalidCredentialsException(); | |
} | |
try { | |
$trying_user = $this->auth->getUserByEmail($email); | |
$user->uid = $trying_user->uid; | |
} catch (\Kreait\Firebase\Exception\Auth\UserNotFound $error) { | |
$created_user = $this->auth->createUser([ | |
'displayName' => $user->name, | |
'email' => $email, | |
'password' => $password, | |
'emailVerified' => true | |
]); | |
$user->uid = $created_user->uid; | |
} | |
$user->save(); | |
// $user->tokens()->delete(); | |
return ['token' => $this->generate_token($user)]; | |
} | |
public function admin_login($email, $password) | |
{ | |
$user = User::where('email', $email)->first(); | |
if ($user?->type != 'admin') { | |
throw new AccessForbiddenException(); | |
} | |
if (empty($user)) { | |
throw new InvalidCredentialsException(); | |
} | |
if (!hash_equals(hash('sha256', $password), $user->password)) { | |
throw new InvalidCredentialsException(); | |
} | |
try { | |
$trying_user = $this->auth->getUserByEmail($email); | |
$user->uid = $trying_user->uid; | |
} catch (\Kreait\Firebase\Exception\Auth\UserNotFound $error) { | |
$created_user = $this->auth->createUser([ | |
'displayName' => $user->name, | |
'email' => $email, | |
'password' => $password, | |
'emailVerified' => true | |
]); | |
$user->uid = $created_user->uid; | |
} | |
$user->save(); | |
// $user->tokens()->delete(); | |
return ['token' => $this->generate_token($user)]; | |
} | |
public function forget_password($email) | |
{ | |
$user = User::where('email', $email)->first(); | |
if (empty($user)) { | |
throw new UserNotFoundException(); | |
} | |
if ($user->password == null) { | |
throw new PasswordNotSetException(); | |
} | |
$this->send_otp($email); | |
return [ | |
'message' => 'User email has been successfully verified and OTP has been sent.' | |
]; | |
} | |
public function verify_otp($otp) | |
{ | |
$user = User::where('otp', $otp)->first(); | |
if (empty($user)) { | |
throw new IncorrectOtpException(); | |
} | |
if ( | |
$user->otp_expiry != null && | |
$user->otp_expiry->diffInSeconds(now(), false) >= 0 | |
) { | |
throw new OtpExpiredException(); | |
} | |
if ($user->email_verified_at == null) { | |
$user->otp = null; | |
$user->otp_expiry = null; | |
$user->otp_retries = 0; | |
} | |
$this->auth->updateUser($user->uid, [ | |
'emailVerified' => true | |
]); | |
$user->tokens()->delete(); | |
$user->email_verified_at = now(); | |
$user->save(); | |
return [ | |
'message' => 'OTP has been successfully verified, call login api to get token' | |
]; | |
} | |
public function resend_otp($email) | |
{ | |
$this->send_otp($email); | |
return ['message' => 'OTP has been successfully sent']; | |
} | |
public function update_forgotten_passowrd($otp, $new_password) | |
{ | |
$user = User::where('otp', $otp)->first(); | |
if (empty($user)) { | |
throw new IncorrectOtpException(); | |
} | |
if ( | |
$user->otp_expiry != null && | |
$user->otp_expiry->diffInSeconds(now(), false) >= 0 | |
) { | |
throw new OtpExpiredException(); | |
} | |
if ($user->password == null) { | |
throw new PasswordNotSetException(); | |
} | |
try { | |
$trying_user = $this->auth->getUserByEmail($user->email); | |
$this->auth->updateUser($trying_user->uid, [ | |
'password' => $new_password | |
]); | |
} catch (\Exception $error) { | |
throw new \Exception( | |
'Error while updating user password in Firebase', | |
500 | |
); | |
} | |
$user->password = hash('sha256', $new_password); | |
$user->save(); | |
ProcessEmail::dispatch( | |
'Password Reset | ' . config('app.name'), | |
"Hi, welcome user!\nYour password has been updated", | |
$user->email | |
); | |
return ['message' => 'User password has been successfully updated']; | |
} | |
public function update_user_password($password, $new_password) | |
{ | |
// if ($user->password == null) { | |
// throw new PasswordNotSetException(); | |
// } | |
if (!hash_equals(hash('sha256', $password), $user->password)) { | |
throw new \Exception('Incorrect password', 401); | |
} | |
try { | |
$this->auth->updateUser($user->uid, [ | |
'password' => $new_password | |
]); | |
} catch (\Exception $error) { | |
throw new \Exception( | |
'Error while updating user password in Firebase', | |
500 | |
); | |
} | |
$user->password = hash('sha256', $new_password); | |
$user->save(); | |
ProcessEmail::dispatch( | |
'Password Reset | ' . config('app.name'), | |
"Hi, welcome user!\nYour password has been updated", | |
$user->email | |
); | |
return ['message' => 'User password has been successfully updated']; | |
} | |
public function logout($user) | |
{ | |
$user->tokens()->delete(); | |
$user->otp = null; | |
$user->save(); | |
return ['message' => 'User successfully logged out']; | |
} | |
public function validate_token() | |
{ | |
return ['is_token_validate' => (bool) auth('sanctum')->check()]; | |
} | |
protected function is_user_already_registered($email) | |
{ | |
return User::where('email', $email)->whereNotNull('email_verified_at') | |
->count() > 0; | |
} | |
protected function generate_token($user) | |
{ | |
$token = $user->createToken($user->email)->plainTextToken; | |
$user->save(); | |
return $token; | |
} | |
protected function send_otp($email, $skip_expiration = false) | |
{ | |
$user = User::where('email', $email)->first(); | |
$otp_expiration_diff = $user->otp_expiry?->diffInSeconds(now(), false); | |
if ($user->otp_expiry != null && $otp_expiration_diff <= 0) { | |
throw new \Exception( | |
"You have exceeded the OTP resend limit, please try again after {$user->otp_expiry?->diff(now())->format("%I:%S")}", | |
429 | |
); | |
} | |
if ($user->otp_retries >= config('app.otp_max_retries')) { | |
$user->otp_retries = 0; | |
$user->save(); | |
} | |
$otp = rand(100000, 999999); | |
ProcessEmail::dispatch( | |
'OTP | ' . config('app.name'), | |
"Hello {$user->name},\n\nHere is your One-Time Password (OTP) for authentication:\n\n{$otp}.\n\nPlease use this code to complete your login or transaction.\n\nThank you,\n" . config('app.name'), | |
); | |
$user->otp = $otp; | |
$user->otp_retries++; | |
if ($skip_expiration) { | |
$user->otp_expiry = null; | |
} else { | |
$user->otp_expiry = now()->addMinutes(config('app.otp_expiration')); | |
} | |
if ($user->otp_retries >= config('app.otp_max_retries')) { | |
$user->otp_expiry = now()->addMinutes(config( | |
'app.otp_expiration_after_max_tries' | |
)); | |
} | |
$user->save(); | |
return $otp; | |
} | |
protected function send_email($subject, $content, $email) | |
{ | |
Mail::raw($content, function ($message) use ($email, $subject) { | |
$message->to($email) | |
->subject($subject); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment