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

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Http\Resources\UserResource;
  4. use App\Models\Business;
  5. use App\Models\Fingerprint;
  6. use App\Models\User;
  7. use App\Notifications\DBNotification;
  8. use App\Notifications\MailNotification;
  9. use Illuminate\Http\JsonResponse;
  10. use Illuminate\Http\Request;
  11. use Illuminate\Session\TokenMismatchException;
  12. use Illuminate\Support\Facades\Auth;
  13. use Illuminate\Support\Facades\Cache;
  14. use Illuminate\Support\Facades\Hash;
  15. use Illuminate\Support\Facades\Notification;
  16. use Illuminate\Support\Str;
  17. use Illuminate\Validation\Rule;
  18. use Laravel\Socialite\Facades\Socialite;
  19. use Symfony\Component\HttpFoundation\Response;
  20. class AuthController extends Controller
  21. {
  22. /**
  23. * Redirect user to google auth procedure
  24. *
  25. * @return mixed
  26. */
  27. public function redirectToGoogle()
  28. {
  29. return Socialite::driver('google')->stateless()->redirect();
  30. }
  31. /**
  32. * Complete user authenticated when return from google auth
  33. *
  34. * @param Request $request
  35. * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
  36. */
  37. public function handleGoogleCallback(Request $request)
  38. {
  39. try {
  40. $user = Socialite::driver('google')->stateless()->user();
  41. $find_user = User::where('email', $user->email)->first();
  42. if (!$find_user)
  43. {
  44. $find_user = User::create($user->user + [
  45. 'password' => Hash::make(Str::random(8)),
  46. 'username' => $user->email,
  47. 'active' => true,
  48. 'has_password' => false
  49. ]);
  50. }
  51. Auth::setUser($find_user);
  52. $finger_print = $this->createFingerPrint();
  53. return redirect('http://localhost:3000/login?token='.$finger_print->token);
  54. } catch (Exception $e) {
  55. dd($e->getMessage());
  56. }
  57. }
  58. /**
  59. * Check email for guidance user state in the app
  60. *
  61. * @param Request $request
  62. * @return JsonResponse
  63. * @throws \Illuminate\Validation\ValidationException
  64. */
  65. public function emailChecking(Request $request)
  66. {
  67. $this->validate($request, [
  68. 'email' => 'required|email',
  69. ]);
  70. $user = User::where('email', $request->email)->first();
  71. if ($user && $user->has_password) {
  72. // email exists in db
  73. // user before set a password
  74. return response()->json(['message' => 'user.exists'], 200);
  75. }
  76. if ($user && !$user->has_password) {
  77. // email exists in db
  78. // user hasn't password (we set password for user)
  79. $this->sendVerification($request->email, 'google');
  80. return response()->json(['message' => 'google'], 200);
  81. }
  82. if (!$user) {
  83. // user not exists in db
  84. $this->sendVerification($request->email, 'register');
  85. return response()->json(['message' => 'register'], 200);
  86. }
  87. // if (Cache::has($request->email)) {
  88. // // email exists in cache
  89. // $this->sendVerification($request->email, Cache::get($request->email)['type']);
  90. // return response()->json(['message' => 'Send email for validation'], 200);
  91. // }
  92. //
  93. // if (!$user && !Cache::has($request->email)) {
  94. // // user not exists in db and cache
  95. // $this->sendVerification($request->email, 'register');
  96. // return response()->json(['message' => 'Send email for validation'], 200);
  97. // }
  98. }
  99. /**
  100. * Login existing user and notify him/her when login from new device
  101. *
  102. * @param Request $request
  103. * @return array|JsonResponse
  104. * @throws \Illuminate\Validation\ValidationException
  105. */
  106. public function login(Request $request)
  107. {
  108. // todo: Logging in from a new device will result in sending a notification
  109. $this->validate($request, [
  110. 'email' => 'required|email|exists:users,email',
  111. 'password' => 'required|string|min:6'
  112. ]);
  113. $user = User::where('email', $request->email)->first();
  114. if ($user && Hash::check($request->password, $user->password)) {
  115. Auth::setUser($user);
  116. // for new device login
  117. $this->loginNotif($this->firstOrNot());
  118. return [
  119. 'auth' => $this->createFingerPrint(),
  120. 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid))
  121. ];
  122. }
  123. return new JsonResponse([
  124. 'message' => trans('auth.failed'),
  125. 'status' => Response::HTTP_NOT_FOUND,
  126. ], Response::HTTP_NOT_FOUND);
  127. }
  128. /**
  129. * Verify link When user click on verification link that before send for user
  130. * In this case user before login with google and now haven't password
  131. *
  132. * @param Request $request
  133. * @return array
  134. * @throws \Illuminate\Validation\ValidationException
  135. */
  136. public function verification(Request $request)
  137. {
  138. $this->validate($request, [
  139. 'email' => 'required|email',
  140. 'signature' => 'required|string',
  141. ]);
  142. $this->checkValidation($request->email, 'google', $request->signature);
  143. Auth::setUser(User::where('email', $request->email)->first());
  144. return [
  145. 'auth' => $this->createFingerPrint(),
  146. 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid))
  147. ];
  148. }
  149. /**
  150. * Send verification email for user
  151. * Used by method in this class
  152. *
  153. * @param $email
  154. * @param $type
  155. */
  156. public function sendVerification($email, $type)
  157. {
  158. $signature = Str::random(30);
  159. Cache::put($email, ['type' => $type, 'signature' => $signature], 3600);
  160. Notification::route('mail', $email)->notify( new MailNotification([
  161. 'greeting' => __('notification.auth.verification.greeting'),
  162. 'subject' => __('notification.auth.verification.subject'),
  163. 'body' => __('notification.auth.verification.new_body'),
  164. 'link' => __('notification.auth.verification.link', [
  165. 'email' => $email,
  166. 'type' => $type,
  167. 'signature' => $signature
  168. ])
  169. ]));
  170. }
  171. /**
  172. * This function used by some method in this class for check validation of signature
  173. *
  174. * @param $email
  175. * @param $type
  176. * @param $signature
  177. * @return JsonResponse
  178. */
  179. public function checkValidation($email, $type, $signature)
  180. {
  181. if (!Cache::has($email) || Cache::get($email)['type'] !== $type || Cache::get($email)['signature'] != $signature)
  182. {
  183. abort(403, 'Validation failed');
  184. }
  185. Cache::forget($email);
  186. }
  187. /**
  188. * User request for forget password if before exists we send email for user
  189. *
  190. * @param Request $request
  191. * @return JsonResponse
  192. * @throws \Illuminate\Validation\ValidationException
  193. */
  194. public function forgetPassword(Request $request)
  195. {
  196. $this->validate($request, [
  197. 'email' => 'required|email|exists:users,email'
  198. ]);
  199. $this->sendVerification($request->email, 'forget');
  200. return response()->json(['message' => 'Send email for validation'], 200);
  201. }
  202. /**
  203. * If user verified in this step we update user password
  204. *
  205. * @param Request $request
  206. * @return JsonResponse
  207. * @throws \Illuminate\Validation\ValidationException
  208. */
  209. public function updatePassword(Request $request)
  210. {
  211. $this->validate($request, [
  212. 'email' => 'required|email',
  213. 'password' => 'required|string|min:8|confirmed',
  214. 'signature' => 'required|string'
  215. ]);
  216. $this->checkValidation($request->email, 'forget', $request->signature);
  217. $user = User::where('email', $request->email)->first();
  218. $user->update([
  219. 'password' => Hash::make($request->password),
  220. 'has_password' => true
  221. ]);
  222. // Auth::setUser($user);
  223. //
  224. // $this->createFingerPrint();
  225. return response()->json(['message' => 'Update successfully you must be login.'], 200);
  226. }
  227. /**
  228. * If user verified we register user and login user
  229. *
  230. * @param Request $request
  231. * @return array
  232. * @throws \Illuminate\Validation\ValidationException
  233. */
  234. public function register(Request $request)
  235. {
  236. $this->validate($request, [
  237. 'name' => 'required|string|max:225|min:2',
  238. 'username' => ['required', Rule::unique('users', 'username')],
  239. 'email' => ['required', 'email', Rule::unique('users', 'email')],
  240. 'password' => 'required|string|min:6',
  241. 'signature' => 'required|string'
  242. ]);
  243. $this->checkValidation($request->email, 'register', $request->signature);
  244. $request->merge(['password' => Hash::make($request->password)]);
  245. $user = User::create($request->all()+ [
  246. 'has_password' => true
  247. ]);
  248. Auth::setUser($user);
  249. return [
  250. 'auth' => $this->createFingerPrint(),
  251. 'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid))
  252. ];
  253. }
  254. /**
  255. * Resend email for user (only one email per minute)
  256. *
  257. * @param Request $request
  258. * @return JsonResponse
  259. * @throws \Illuminate\Validation\ValidationException
  260. */
  261. public function resendLink(Request $request)
  262. {
  263. $this->validate($request, [
  264. 'email' => 'required|email',
  265. 'type' => 'required|string'
  266. ]);
  267. $user_db = User::where('email', $request->email)->first();
  268. $user_cache = Cache::get($request->email);
  269. if ($user_db || $user_cache) {
  270. $this->sendVerification($request->email, $request->type);
  271. return response()->json(['message' => 'Link resend successfully'], 200);
  272. }
  273. abort(403);
  274. }
  275. /**
  276. * This function just used by front for checking validation of link whit signature
  277. *
  278. * @param $email
  279. * @param $type
  280. * @param $signature
  281. * @return JsonResponse
  282. */
  283. public function linkVerification(Request $request)
  284. {
  285. if (!Cache::has($request->email) || Cache::get($request->email)['type'] !== $request->type || Cache::get($request->email)['signature'] != $request->signature)
  286. {
  287. abort(403, 'Validation failed');
  288. }
  289. return response()->json(['message' => 'Verified successfully. go on'], 200);
  290. }
  291. /**
  292. * Create new token finger print when user login from new device or register
  293. *
  294. * @return mixed
  295. */
  296. public function createFingerPrint()
  297. {
  298. $attributes = [
  299. 'agent' => request()->getAgent(),
  300. 'ip' => request()->getClientIp(),
  301. 'os' => request()->getOS(),
  302. 'latitude' => \request()->getLocation()->getAttribute('lat'),
  303. 'longitude' => \request()->getLocation()->getAttribute('lon'),
  304. ];
  305. $values = [
  306. 'token' => Str::random(60)
  307. ];
  308. return Auth::user()->fingerprints()->firstOrCreate($attributes, $attributes + $values);
  309. }
  310. /**
  311. * Check user login from new device or not
  312. * Used by some methode in this class
  313. *
  314. * @return mixed
  315. */
  316. public function firstOrNot()
  317. {
  318. return Auth::user()->fingerprints()->where([
  319. ['agent', '!=',request()->getAgent()],
  320. ['ip', '!=',request()->getClientIp()],
  321. ['os', '!=',request()->getOS()],
  322. ['latitude', '!=',\request()->getLocation()->getAttribute('lat')],
  323. ['longitude', '!=',\request()->getLocation()->getAttribute('lon')],
  324. ])->exists();
  325. }
  326. /**
  327. * Send notification for user that login from new device
  328. *
  329. * @param $send
  330. */
  331. public function loginNotif($send)
  332. {
  333. if ($send) {
  334. Notification::send(Auth::user(), new MailNotification([
  335. 'greeting' => 'hi',
  336. 'subject' => 'login with another device',
  337. 'body' => 'Warning someone login to your account with new device. check it and dont worry',
  338. ]));
  339. Notification::send(Auth::user(), new DBNotification([
  340. 'body' => 'Warning someone login to your account with new device. check it and dont worry',
  341. ]));
  342. }
  343. }
  344. /**
  345. * Return authenticated user
  346. *
  347. * @return UserResource
  348. */
  349. public function auth()
  350. {
  351. return new UserResource(Auth::user());
  352. }
  353. /**
  354. * Return authenticated user with business info
  355. *
  356. * @return array
  357. */
  358. public function authWithInfo()
  359. {
  360. return [
  361. 'auth' => new UserResource(Auth::user()),
  362. 'businesses' => Auth::user()->businesses->keyBy('id') ->map(fn($b, $bid) => Business::info($bid))
  363. ];
  364. }
  365. /**
  366. * When user accept google fcm push notification, google grant token to user
  367. * This token save in user finger print for push notification
  368. *
  369. * @param Request $request
  370. * @return array
  371. */
  372. public function updateFcmToken(Request $request)
  373. {
  374. Auth::user()->fingerprints()->where(
  375. [
  376. ['agent', request()->getAgent()],
  377. ['ip', request()->getClientIp()],
  378. ['os', request()->getOS()],
  379. ['latitude', \request()->getLocation()->getAttribute('lat')],
  380. ['longitude', \request()->getLocation()->getAttribute('lon')],
  381. ]
  382. )->firstOrFail()->update([
  383. 'fcm_token' => $request->fcm_token
  384. ]);
  385. return $this->authWithInfo();
  386. }
  387. /**
  388. * @param Request $request
  389. * @return mixed
  390. * @throws TokenMismatchException
  391. */
  392. public function logout(Request $request)
  393. {
  394. $token = $request->bearerToken();
  395. if (blank($token)) {
  396. return new JsonResponse([
  397. 'message' => 'Not authorized request.',
  398. 'status' => Response::HTTP_UNAUTHORIZED
  399. ]);
  400. }
  401. /** @var Fingerprint $token */
  402. $token = Auth::user()->fingerprints()->firstWhere([
  403. 'token' => $token,
  404. ]);
  405. if ($token) {
  406. return $token->delete();
  407. }
  408. throw new TokenMismatchException('Invalid token!');
  409. }
  410. /**
  411. * @param string $token
  412. * @throws TokenMismatchException
  413. */
  414. public function revoke(string $token)
  415. {
  416. /** @var Fingerprint $token */
  417. $token = Fingerprint::firstWhere([
  418. 'token' => $token,
  419. ]);
  420. if ($token) {
  421. return $token->delete();
  422. }
  423. throw new TokenMismatchException();
  424. }
  425. }