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.

446 lines
14 KiB

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