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