|
|
<?php
namespace App\Http\Controllers;
use App\Http\Resources\UserResource; use App\Models\Business; use App\Models\Fingerprint; use App\Models\User; use App\Notifications\DBNotification; use App\Notifications\MailNotification; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Session\TokenMismatchException; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Notification; use Illuminate\Support\Str; use Illuminate\Validation\Rule; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response;
class AuthController extends Controller { /** * Redirect user to google auth procedure * * @return mixed */ public function redirectToGoogle() { return Socialite::driver('google')->stateless()->redirect(); }
/** * Complete user authenticated when return from google auth * * @param Request $request * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ public function handleGoogleCallback(Request $request) { try {
$user = Socialite::driver('google')->stateless()->user(); $find_user = User::where('email', $user->email)->first();
if (!$find_user) {
$find_user = User::create($user->user + [ 'password' => Hash::make(Str::random(8)), 'username' => $user->email, 'active' => true, 'has_password' => false ]);
}
Auth::setUser($find_user);
$finger_print = $this->createFingerPrint();
return redirect('http://localhost:3000/login?token='.$finger_print->token);
} catch (Exception $e) { dd($e->getMessage()); } }
/** * Check email for guidance user state in the app * * @param Request $request * @return JsonResponse * @throws \Illuminate\Validation\ValidationException */ public function emailChecking(Request $request) { $this->validate($request, [ 'email' => 'required|email', ]);
$user = User::where('email', $request->email)->first();
if ($user && $user->has_password) { // email exists in db
// user before set a password
return response()->json(['message' => 'user.exists'], 200); }
if ($user && !$user->has_password) { // email exists in db
// user hasn't password (we set password for user)
$this->sendVerification($request->email, 'google'); return response()->json(['message' => 'google'], 200); }
if (!$user) { // user not exists in db
$this->sendVerification($request->email, 'register'); return response()->json(['message' => 'register'], 200); }
// if (Cache::has($request->email)) {
// // email exists in cache
// $this->sendVerification($request->email, Cache::get($request->email)['type']);
// return response()->json(['message' => 'Send email for validation'], 200);
// }
//
// if (!$user && !Cache::has($request->email)) {
// // user not exists in db and cache
// $this->sendVerification($request->email, 'register');
// return response()->json(['message' => 'Send email for validation'], 200);
// }
}
/** * Login existing user and notify him/her when login from new device * * @param Request $request * @return array|JsonResponse * @throws \Illuminate\Validation\ValidationException */ public function login(Request $request) { // todo: Logging in from a new device will result in sending a notification
$this->validate($request, [ 'email' => 'required|email|exists:users,email', 'password' => 'required|string|min:6' ]);
$user = User::where('email', $request->email)->first(); if ($user && Hash::check($request->password, $user->password)) { Auth::setUser($user);
// for new device login
$this->loginNotif($this->firstOrNot());
return [ 'auth' => $this->createFingerPrint(), 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid)) ]; }
return new JsonResponse([ 'message' => trans('auth.failed'), 'status' => Response::HTTP_NOT_FOUND, ], Response::HTTP_NOT_FOUND); }
/** * Verify link When user click on verification link that before send for user * In this case user before login with google and now haven't password * * @param Request $request * @return array * @throws \Illuminate\Validation\ValidationException */ public function verification(Request $request) { $this->validate($request, [ 'email' => 'required|email', 'signature' => 'required|string', ]);
$this->checkValidation($request->email, 'google', $request->signature);
Auth::setUser(User::where('email', $request->email)->first());
return [ 'auth' => $this->createFingerPrint(), 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid)) ];
}
/** * Send verification email for user * Used by method in this class * * @param $email * @param $type */ public function sendVerification($email, $type) { $signature = Str::random(30);
Cache::put($email, ['type' => $type, 'signature' => $signature], 3600);
Notification::route('mail', $email)->notify( new MailNotification([ 'greeting' => __('notification.auth.verification.greeting'), 'subject' => __('notification.auth.verification.subject'), 'body' => __('notification.auth.verification.new_body'), 'link' => __('notification.auth.verification.link', [ 'email' => $email, 'type' => $type, 'signature' => $signature ]) ])); }
/** * This function used by some method in this class for check validation of signature * * @param $email * @param $type * @param $signature * @return JsonResponse */ public function checkValidation($email, $type, $signature) { if (!Cache::has($email) || Cache::get($email)['type'] !== $type || Cache::get($email)['signature'] != $signature) { abort(403, 'Validation failed'); } Cache::forget($email); }
/** * User request for forget password if before exists we send email for user * * @param Request $request * @return JsonResponse * @throws \Illuminate\Validation\ValidationException */ public function forgetPassword(Request $request) { $this->validate($request, [ 'email' => 'required|email|exists:users,email' ]);
$this->sendVerification($request->email, 'forget');
return response()->json(['message' => 'Send email for validation'], 200); }
/** * If user verified in this step we update user password * * @param Request $request * @return JsonResponse * @throws \Illuminate\Validation\ValidationException */ public function updatePassword(Request $request) { $this->validate($request, [ 'email' => 'required|email', 'password' => 'required|string|min:8|confirmed', 'signature' => 'required|string' ]);
$this->checkValidation($request->email, 'forget', $request->signature);
$user = User::where('email', $request->email)->first();
$user->update([ 'password' => Hash::make($request->password), 'has_password' => true ]);
// Auth::setUser($user);
//
// $this->createFingerPrint();
return response()->json(['message' => 'Update successfully you must be login.'], 200); }
/** * If user verified we register user and login user * * @param Request $request * @return array * @throws \Illuminate\Validation\ValidationException */ public function register(Request $request) { $this->validate($request, [ 'name' => 'required|string|max:225|min:2', 'username' => ['required', Rule::unique('users', 'username')], 'email' => ['required', 'email', Rule::unique('users', 'email')], 'password' => 'required|string|min:6', 'signature' => 'required|string' ]);
$this->checkValidation($request->email, 'register', $request->signature);
$request->merge(['password' => Hash::make($request->password)]);
$user = User::create($request->all()+ [ 'has_password' => true ]);
Auth::setUser($user);
return [ 'auth' => $this->createFingerPrint(), 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid)) ]; }
/** * Resend email for user (only one email per minute) * * @param Request $request * @return JsonResponse * @throws \Illuminate\Validation\ValidationException */ public function resendLink(Request $request) { $this->validate($request, [ 'email' => 'required|email', 'type' => 'required|string' ]);
$user_db = User::where('email', $request->email)->first(); $user_cache = Cache::get($request->email);
if ($user_db || $user_cache) { $this->sendVerification($request->email, $request->type); return response()->json(['message' => 'Link resend successfully'], 200); }
abort(403); }
/** * This function just used by front for checking validation of link whit signature * * @param $email * @param $type * @param $signature * @return JsonResponse */ public function linkVerification(Request $request) { if (!Cache::has($request->email) || Cache::get($request->email)['type'] !== $request->type || Cache::get($request->email)['signature'] != $request->signature) { abort(403, 'Validation failed'); } return response()->json(['message' => 'Verified successfully. go on'], 200); }
/** * Create new token finger print when user login from new device or register * * @return mixed */ public function createFingerPrint() { $attributes = [ 'agent' => request()->getAgent(), 'ip' => request()->getClientIp(), 'os' => request()->getOS(), 'latitude' => \request()->getLocation()->getAttribute('lat'), 'longitude' => \request()->getLocation()->getAttribute('lon'), ];
$values = [ 'token' => Str::random(60) ];
return Auth::user()->fingerprints()->firstOrCreate($attributes, $attributes + $values); }
/** * Check user login from new device or not * Used by some methode in this class * * @return mixed */ public function firstOrNot() { return Auth::user()->fingerprints()->where([ ['agent', '!=',request()->getAgent()], ['ip', '!=',request()->getClientIp()], ['os', '!=',request()->getOS()], ['latitude', '!=',\request()->getLocation()->getAttribute('lat')], ['longitude', '!=',\request()->getLocation()->getAttribute('lon')], ])->exists(); }
/** * Send notification for user that login from new device * * @param $send */ public function loginNotif($send) { if ($send) { Notification::send(Auth::user(), new MailNotification([ 'greeting' => 'hi', 'subject' => 'login with another device', 'body' => 'Warning someone login to your account with new device. check it and dont worry', ])); Notification::send(Auth::user(), new DBNotification([ 'body' => 'Warning someone login to your account with new device. check it and dont worry', ])); } }
/** * Return authenticated user * * @return UserResource */ public function auth() { return new UserResource(Auth::user()); }
/** * Return authenticated user with business info * * @return array */ public function authWithInfo() { return [ 'auth' => new UserResource(Auth::user()), 'businesses' => Auth::user()->businesses->keyBy('id') ->map(fn($b, $bid) => Business::info($bid)) ]; }
/** * When user accept google fcm push notification, google grant token to user * This token save in user finger print for push notification * * @param Request $request * @return array */ public function updateFcmToken(Request $request) { Auth::user()->fingerprints()->where( [ ['agent', request()->getAgent()], ['ip', request()->getClientIp()], ['os', request()->getOS()], ['latitude', \request()->getLocation()->getAttribute('lat')], ['longitude', \request()->getLocation()->getAttribute('lon')], ] )->firstOrFail()->update([ 'fcm_token' => $request->fcm_token ]); return $this->authWithInfo(); }
/** * @param Request $request * @return mixed * @throws TokenMismatchException */ public function logout(Request $request) { $token = $request->bearerToken();
if (blank($token)) { return new JsonResponse([ 'message' => 'Not authorized request.', 'status' => Response::HTTP_UNAUTHORIZED ]); }
/** @var Fingerprint $token */ $token = Auth::user()->fingerprints()->firstWhere([ 'token' => $token, ]);
if ($token) { return $token->delete(); }
throw new TokenMismatchException('Invalid token!'); }
/** * @param string $token * @throws TokenMismatchException */ public function revoke(string $token) { /** @var Fingerprint $token */ $token = Fingerprint::firstWhere([ 'token' => $token, ]);
if ($token) { return $token->delete(); }
throw new TokenMismatchException(); }
}
|