You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

494 lines
15 KiB

<?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();
}
}