Browse Source

Merge pull request 'mahdi' (#7) from mahdi into master

Reviewed-on: #7
master
omidshj 4 years ago
parent
commit
52eb687efe
  1. 6
      .env.example
  2. 115
      app/Channels/Messages/SmsMessage.php
  3. 132
      app/Channels/SmsChannel.php
  4. 12
      app/Enums/sms.php
  5. 5
      app/Enums/tables.php
  6. 470
      app/Http/Controllers/AuthController.php
  7. 301
      app/Http/Controllers/OldAuthController.php
  8. 10
      app/Listeners/NotifHandler.php
  9. 4
      app/Models/Business.php
  10. 12
      app/Models/User.php
  11. 2
      app/Notifications/DBNotification.php
  12. 2
      app/Notifications/MailNotification.php
  13. 42
      app/Notifications/SmsNotification.php
  14. 4
      app/Notifications/SocketNotification.php
  15. 13
      app/Providers/AppServiceProvider.php
  16. 15
      app/Utilities/HelperClass/NotificationHelper.php
  17. 16
      config/smsirlaravel.php
  18. 1
      database/migrations/2020_08_18_085016_create_users_table.php
  19. 1
      database/migrations/2021_03_08_114700_create_notifications_table.php
  20. 19
      docker-compose.yml
  21. 2521
      filebeat.reference.yml
  22. 232
      filebeat.yml
  23. 19
      my.cnf
  24. 22
      resources/lang/fa/notification.php
  25. 10
      routes/api.php

6
.env.example

@ -36,7 +36,7 @@ MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_ADDRESS=from@example.com
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
@ -54,4 +54,8 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
FCM_KEY=null
SOCKET_URL=192.168.x.x:3030
SMSIR_WEBSERVICE_URL=https://ws.sms.ir/
SMSIR_API_KEY=
SMSIR_SECRET_KEY=
SMSIR_LINE_NUMBER=

115
app/Channels/Messages/SmsMessage.php

@ -0,0 +1,115 @@
<?php
namespace App\Channels\Messages;
class SmsMessage
{
/**
* The devices token to send the message from.
*
* @var array|string
*/
public $to;
/**
* The data of the Sms message.
*
* @var array
*/
public $data;
/**
* The params that we define in sms.ir template
* when we use ultraFastSend method
*
* @var array
*/
public $params;
/**
* The id of template we define in sms.ir
* when we use ultraFastSend method
*
* @var string
*/
public $template_id;
/**
* The generated verification code.
* when we use sendVerification method.
*
* @var string
*/
public $verification_code;
/**
* Set the devices token to send the message from.
*
* @param array|string $to
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* Set the data of the sms message.
*
* @param array $data
* @return $this
*/
public function data(array $data)
{
$this->data = $data;
return $this;
}
/**
* Set the params of the sms message when we use ultraFastSend method
*
* @param array|mixed $params
* @return $this
*/
public function params($params)
{
foreach ($params as $key => $value) {
$this->params[] = ['Parameter' => $key, 'ParameterValue' => $value];
}
return $this;
}
/**
* Set the template_id of the sms message when we use ultraFastSend method
*
* @param string $template_id
* @return $this
*/
public function templateId($template_id)
{
$this->template_id = $template_id;
return $this;
}
/**
* Set verification_code of sms message when we use sendVerification method
*
* @param string $verification_code
* @return $this
*/
public function verificationCode(string $verification_code)
{
$this->verification_code = $verification_code;
return $this;
}
}

132
app/Channels/SmsChannel.php

@ -0,0 +1,132 @@
<?php
namespace App\Channels;
use App\Channels\Messages\SmsMessage;
use Illuminate\Notifications\Notification;
use GuzzleHttp\Client as HttpClient;
class SmsChannel
{
/**
* The API URL for sms.
*
* @var string
*/
protected $sms_url;
/**
* The api key for sms inside sms.ir panel.
*
* @var string
*/
protected $api_key;
/**
* The secret key for sms inside sms.ir panel.
*
* @var string
*/
protected $secret_key;
/**
* The HTTP client instance.
*
* @var \GuzzleHttp\Client
*/
protected $http;
/**
* Create a new Socket channel instance.
*
* @param \GuzzleHttp\Client $http
* @return void
*/
public function __construct(HttpClient $http, string $sms_url, string $api_key, string $secret_key)
{
$this->http = $http;
$this->sms_url = $sms_url;
$this->api_key = $api_key;
$this->secret_key = $secret_key;
}
/**
* Send the given notification.
*
* @param mixed $notifiable
* @param \Illuminate\Notifications\Notification $notification
* @return void
*/
public function send($notifiable, Notification $notification)
{
$message = $notification->toSms($notifiable);
$type = $notification->getType();
$message->to($notifiable->routeNotificationFor('sms', $notification));
if (! $message->to || (! ($message->params && $message->template_id) && ! $message->verification_code)) {
return;
}
try {
$this->http->post($this->sms_url . 'api/' . $type , [
'headers' => [
'x-sms-ir-secure-token'=> $this->getToken()
],
'connect_timeout' => 30,
'json' => $this->buildJsonPayload($message, $type),
]);
} catch (\GuzzleHttp\Exception\ConnectException $e) {
report($e);
}
}
/**
* This method used in every request to get the token at first.
*
* @return mixed - the Token for use api
*/
protected function getToken()
{
$body = [
'UserApiKey' => $this->api_key,
'SecretKey' => $this->secret_key,
'System' => 'laravel_v_1_4'
];
try {
$result = $this->http->post( $this->sms_url . 'api/Token',['json'=>$body,'connect_timeout'=>30]);
} catch (\GuzzleHttp\Exception\ConnectException $e) {
report($e);
return;
}
return json_decode($result->getBody(),true)['TokenKey'];
}
/**
* Create sms json payload based on type
*
* @param SmsMessage $message
* @param $type
* @return array
*/
protected function buildJsonPayload(SmsMessage $message, $type) {
if ($type === 'UltraFastSend') {
return[
'ParameterArray' => $message->params,
'TemplateId' => $message->template_id,
'Mobile' => $message->to
];
}
if ($type === 'VerificationCode') {
return[
'Code' => $message->data,
'MobileNumber' => $message->to
];
}
}
}

12
app/Enums/sms.php

@ -0,0 +1,12 @@
<?php
return [
'types' => [
'verification_code' => [
'name' => 'VerificationCode'
],
'ultra_fast_send' => [
'name' => 'ultraFastSend'
]
],
];

5
app/Enums/tables.php

@ -63,6 +63,11 @@ return [
'name' => 'Works',
'singular_name' => 'Work',
],
'users' => [
'id' => 100,
'name' => 'Users',
'singular_name' => 'Users',
],
//Relation Table's
'business_user' => [

470
app/Http/Controllers/AuthController.php

@ -2,28 +2,42 @@
namespace App\Http\Controllers;
use App\Models\User;
use App\Http\Resources\UserResource;
use App\Models\Business;
use App\Models\Fingerprint;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Models\User;
use App\Notifications\DBNotification;
use App\Notifications\MailNotification;
use Illuminate\Http\JsonResponse;
use App\Http\Resources\UserResource;
use Illuminate\Http\Request;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
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 Illuminate\Session\TokenMismatchException;
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 {
@ -31,33 +45,83 @@ class AuthController extends Controller
$user = Socialite::driver('google')->stateless()->user();
$find_user = User::where('email', $user->email)->first();
if ($find_user) {
$find_user->update([
'active' => true
]);
Auth::setUser($find_user);
} else {
if (!$find_user)
{
$user = User::create($user->user + [
'password' => Hash::make('google-login-user'),
$find_user = User::create($user->user + [
'password' => Hash::make(Str::random(8)),
'username' => $user->email,
'active' => true
'active' => true,
'has_password' => false
]);
Auth::setUser($user);
}
}
$finger_print = $this->createFingerPrint();
return redirect('http://localhost:3000/login?token='.$finger_print->token);
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
@ -70,6 +134,9 @@ class AuthController extends Controller
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))
@ -82,156 +149,271 @@ class AuthController extends Controller
], Response::HTTP_NOT_FOUND);
}
public function register(Request $request)
/**
* 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, [
'name' => 'required|string|max:225|min:2',
'username' => ['required', Rule::unique('users', 'username')],
'email' => ['required', 'email', Rule::unique('users', 'email')],
'password' => 'required|string|min:8'
]);
$request->merge(['password' => Hash::make($request->password)]);
$code_data = ['verification_code' => $this->sendVerificationCode()];
$method_data = ['method' => 'registerMain'];
Cache::put($request->email, $request->all() + $code_data + $method_data, 3600); // remain one hour
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
'email' => 'required|email',
'signature' => 'required|string',
]);
}
$this->checkValidation($request->email, 'google', $request->signature);
public function registerMain($user_info)
{
$user = User::create($user_info);
Auth::setUser(User::where('email', $request->email)->first());
Auth::setUser($user);
return [
'auth' => $this->createFingerPrint(),
'businesses' => Auth::user()->businesses->keyBy('id')->map(fn($b, $bid) => Business::info($bid))
];
return $this->createFingerPrint();
}
public function sendVerificationCode($contact_way = null)
/**
* Send verification email for user
* Used by method in this class
*
* @param $email
* @param $type
*/
public function sendVerification($email, $type)
{
$verification_code = 1234; // rand(10001, 99999)
//send code for user with contact way
return $verification_code;
$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
])
]));
}
public function verification(Request $request)
/**
* 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($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
if (!Cache::has($email) || Cache::get($email)['type'] !== $type || Cache::get($email)['signature'] != $signature)
{
abort(403, 'Validation failed');
}
$user_info = Cache::get($request->email);
$this->validate($request, [
'email' => 'required|email',
'verification_code' => 'required|string|min:4|max:4|in:'.$user_info['verification_code']
]);
Cache::forget($request->email);
return isset($user_info['method']) ?
call_user_func('self::'.$user_info['method'], $user_info) :
\response()->json(['message' => 'Code verified successfully.'], Response::HTTP_OK,);
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'
]);
$code_data = ['verification_code' => $this->sendVerificationCode()];
Cache::put($request->email, $request->all() + $code_data, 3600); // remain one hour
$this->sendVerification($request->email, 'forget');
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
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)
{
if (!Cache::has($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
}
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|string|min:8|confirmed',
'verification_code' => 'required|string|min:4|max:4|in:'.Cache::get($request->email)['verification_code']
'signature' => 'required|string'
]);
$this->checkValidation($request->email, 'forget', $request->signature);
$user = User::where('email', $request->email)->first();
$user->update([
'password' => Hash::make($request->password)
'password' => Hash::make($request->password),
'has_password' => true
]);
Auth::setUser($user);
// Auth::setUser($user);
//
// $this->createFingerPrint();
return $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 mixed
* @throws TokenMismatchException
* @return array
* @throws \Illuminate\Validation\ValidationException
*/
public function logout(Request $request)
public function register(Request $request)
{
$token = $request->bearerToken();
$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'
]);
if (blank($token)) {
return new JsonResponse([
'message' => 'Not authorized request.',
'status' => Response::HTTP_UNAUTHORIZED
$this->checkValidation($request->email, 'register', $request->signature);
$request->merge(['password' => Hash::make($request->password)]);
$user = User::create($request->all()+ [
'has_password' => true
]);
}
/** @var Fingerprint $token */
$token = Auth::user()->fingerprints()->firstWhere([
'token' => $token,
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'
]);
if ($token) {
return $token->delete();
$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);
}
throw new TokenMismatchException('Invalid token!');
abort(403);
}
/**
* @param string $token
* @throws TokenMismatchException
* This function just used by front for checking validation of link whit signature
*
* @param $email
* @param $type
* @param $signature
* @return JsonResponse
*/
public function revoke(string $token)
public function linkVerification(Request $request)
{
/** @var Fingerprint $token */
$token = Fingerprint::firstWhere([
'token' => $token,
]);
if ($token) {
return $token->delete();
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);
}
throw new TokenMismatchException();
/**
* 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 [
@ -240,15 +422,13 @@ class AuthController extends Controller
];
}
public function delete(Request $request)
{
Auth::user()->fingerprints()->delete();
unset(Auth::user()->token);
Auth::user()->delete();
return 'success';
}
/**
* 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(
@ -265,20 +445,50 @@ class AuthController extends Controller
return $this->authWithInfo();
}
public function createFingerPrint()
/**
* @param Request $request
* @return mixed
* @throws TokenMismatchException
*/
public function logout(Request $request)
{
$attributes = [
'agent' => request()->getAgent(),
'ip' => request()->getClientIp(),
'os' => request()->getOS(),
'latitude' => \request()->getLocation()->getAttribute('lat'),
'longitude' => \request()->getLocation()->getAttribute('lon'),
];
$token = $request->bearerToken();
$values = [
'token' => Str::random(60)
];
if (blank($token)) {
return new JsonResponse([
'message' => 'Not authorized request.',
'status' => Response::HTTP_UNAUTHORIZED
]);
}
return Auth::user()->fingerprints()->firstOrCreate($attributes, $attributes + $values);
/** @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();
}
}

301
app/Http/Controllers/OldAuthController.php

@ -0,0 +1,301 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Models\Business;
use App\Models\Fingerprint;
use App\Notifications\MailNotification;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Http\JsonResponse;
use App\Http\Resources\UserResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Cache;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Session\TokenMismatchException;
use phpDocumentor\Reflection\Type;
use Symfony\Component\HttpFoundation\Response;
class OldAuthController extends Controller
{
public function redirectToGoogle()
{
return Socialite::driver('google')->stateless()->redirect();
}
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->update([
'active' => true
]);
Auth::setUser($find_user);
} else {
$user = User::create($user->user + [
'password' => Hash::make('google-login-user'),
'username' => $user->email,
'active' => true
]);
Auth::setUser($user);
}
$finger_print = $this->createFingerPrint();
return redirect('http://localhost:3000/login?token='.$finger_print->token);
} catch (Exception $e) {
dd($e->getMessage());
}
}
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);
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);
}
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:8'
]);
$request->merge(['password' => Hash::make($request->password)]);
$code_data = ['verification_code' => $this->sendVerificationCode(\request('email'), 'register')];
$method_data = ['method' => 'registerMain'];
Cache::put($request->email, $request->all() + $code_data + $method_data, 3600); // remain one hour
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
}
public function registerMain($user_info)
{
$user = User::create($user_info);
Auth::setUser($user);
return $this->createFingerPrint();
}
public function sendVerificationCode($contact_way, $type)
{
$verification_code = rand(10001, 99999);
Notification::route('mail', $contact_way)->notify( new MailNotification([
'greeting' => __('notification.auth.verification.greeting'),
'subject' => __('notification.auth.verification.subject'),
'body' => __('notification.auth.verification.body', ['code' => $verification_code]),
'link' => __('notification.auth.verification.link', ['email' => $contact_way, 'type' => $type]),
]));
return $verification_code;
}
public function verification(Request $request)
{
if (!Cache::has($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
}
$user_info = Cache::get($request->email);
$this->validate($request, [
'email' => 'required|email',
'verification_code' => 'required|string|min:4|max:4|in:'.$user_info['verification_code']
]);
// Cache::forget($request->email);
if (isset($user_info['method'])) {
Cache::forget($request->email);
return call_user_func('self::'.$user_info['method'], $user_info);
}
return \response()->json(['message' => 'Code verified successfully.'], Response::HTTP_OK,);
// return isset($user_info['method']) ?
// call_user_func('self::'.$user_info['method'], $user_info) :
// \response()->json(['message' => 'Code verified successfully.'], Response::HTTP_OK,);
}
public function forgetPassword(Request $request)
{
$this->validate($request, [
'email' => 'required|email|exists:users,email'
]);
$code_data = ['verification_code' => $this->sendVerificationCode(\request('email', 'forget'))];
Cache::put($request->email, $request->all() + $code_data, 3600); // remain one hour
return \response()->json([
'message' => 'Code send for user and user must be verified.'],
Response::HTTP_OK);
}
public function updatePassword(Request $request)
{
if (!Cache::has($request->email)) {
return \response()->json(['message' => 'Code expired.'], Response::HTTP_BAD_REQUEST);
}
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|string|min:8|confirmed',
'verification_code' => 'required|string|min:4|max:4|in:'.Cache::get($request->email)['verification_code']
]);
$user = User::where('email', $request->email)->first();
$user->update([
'password' => Hash::make($request->password)
]);
Auth::setUser($user);
Cache::forget($request->email);
return $this->createFingerPrint();
}
/**
* @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();
}
public function auth()
{
return new UserResource(Auth::user());
}
public function authWithInfo()
{
return [
'auth' => new UserResource(Auth::user()),
'businesses' => Auth::user()->businesses->keyBy('id') ->map(fn($b, $bid) => Business::info($bid))
];
}
public function delete(Request $request)
{
Auth::user()->fingerprints()->delete();
unset(Auth::user()->token);
Auth::user()->delete();
return 'success';
}
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();
}
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);
}
}

10
app/Listeners/NotifHandler.php

@ -3,8 +3,10 @@
namespace App\Listeners;
use App\Events\ModelSaved;
use App\Notifications\SocketNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Notification;
class NotifHandler
{
@ -29,8 +31,14 @@ class NotifHandler
$message = json_decode($event->message);
$event_class = 'App\Events\\'.enum('tables.'.$message->data->table_name.'.singular_name').enum('cruds.inverse.'.$message->data->crud_id.'.name');
if (class_exists($event_class)) {
// event(new ('App\Events\\'.$event_class($message)));
$event_class::dispatch($message);
}
if (auth()->user()) {
Notification::send(auth()->user(), new SocketNotification(
[
'message' => enum('tables.'.$message->data->table_name.'.singular_name').enum('cruds.inverse.'.$message->data->crud_id.'.name'),
'payload'=> request('_business_info') ?? null
]));
}
}
}

4
app/Models/Business.php

@ -5,6 +5,8 @@ namespace App\Models;
use App\Models\File;
use App\Models\Model;
use App\Models\SoftDeletes;
use App\Notifications\SocketNotification;
use Illuminate\Support\Facades\Notification;
use Illuminate\Validation\Rule;
use Illuminate\Http\UploadedFile;
use Spatie\MediaLibrary\HasMedia;
@ -234,6 +236,8 @@ class Business extends Model implements HasMedia
Cache::put('business_info'.$businessId , $business_info, config('app.cache_ttl'));
Notification::send(auth()->user(), new SocketNotification(['message' => 'business info update','payload'=>$business_info]));
return $business_info;
}

12
app/Models/User.php

@ -29,7 +29,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
'has_avatar' => 'boolean',
];
protected $fillable = ['name', 'email','mobile', 'username','password','active','has_avatar'];
protected $fillable = ['name', 'email','mobile', 'username','password','active','has_avatar', 'has_password'];
protected $fillable_relations = ['projects'];
@ -60,6 +60,16 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
return request('_business_info')['id'] ?? null;
}
/**
* Specifies the user's phone number
*
* @return string
*/
public function routeNotificationForSms()
{
return $this->mobile;
}
public function updateRelations()
{
// projects relations

2
app/Notifications/DBNotification.php

@ -29,7 +29,7 @@ class DBNotification extends Notification
*/
public function via($notifiable)
{
return ['db'];
return ['database'];
}
/**

2
app/Notifications/MailNotification.php

@ -73,6 +73,6 @@ class MailNotification extends Notification implements ShouldQueue
->greeting($this->message['greeting'])
->line($this->message['body'])
->subject($this->message['subject'])
->action('Notification Action', url('/'));
->action('بیشتر', $this->message['link'] ?? url('/'));
}
}

42
app/Notifications/SmsNotification.php

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Channels\Messages\SmsMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@ -13,14 +14,24 @@ class SmsNotification extends Notification
public $message;
/**
* Show type of sms notification.
* [ultraFastSend or sendVerification ]
*
* @var mixed|string
*/
public $type;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($message)
public function __construct($message, $type = null)
{
$this->message = $message;
$this->type = $type ?? enum('sms.types.verification_code.name');
}
/**
@ -31,33 +42,26 @@ class SmsNotification extends Notification
*/
public function via($notifiable)
{
return ['mail'];
return ['sms'];
}
/**
* Get the mail representation of the notification.
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
* @return array
*/
public function toMail($notifiable)
public function toSms($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
return (new SmsMessage())
->params($this->message['params'] ?? [])
->templateId($this->message['template_id'] ?? null)
->verificationCode($this->message['verification_code'] ?? null);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
public function getType()
{
return [
//
];
return $this->type;
}
}

4
app/Notifications/SocketNotification.php

@ -45,8 +45,8 @@ class SocketNotification extends Notification
{
return (new SocketMessage())
->data([
'title' => $this->message['title'],
'body' => $this->message['body'],
'message' => $this->message['message'],
'payload' => $this->message['payload'],
]);
}
}

13
app/Providers/AppServiceProvider.php

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Channels\DBChannel;
use App\Channels\FcmChannel;
use App\Channels\SmsChannel;
use App\Channels\SocketChannel;
use Illuminate\Notifications\ChannelManager;
use Illuminate\Support\Facades\Notification;
@ -26,8 +27,16 @@ class AppServiceProvider extends ServiceProvider
$service->extend('socket', function ($app) {
return new SocketChannel(new HttpClient, config('socket.url'));
});
$service->extend('db', function ($app) {
return new DBChannel();
// $service->extend('db', function ($app) {
// return new DBChannel();
// });
$service->extend('sms', function ($app) {
return new SmsChannel(
new HttpClient,
config('smsirlaravel.webservice-url'),
config('smsirlaravel.api-key'),
config('smsirlaravel.secret-key'),
);
});
});
}

15
app/Utilities/HelperClass/NotificationHelper.php

@ -8,11 +8,14 @@ use App\Notifications\DBNotification;
use App\Notifications\FcmNotification;
use App\Notifications\MailNotification;
use App\Notifications\SocketNotification;
use App\Notifications\SmsNotification;
use Illuminate\Support\Facades\Notification;
class NotificationHelper
{
protected $notif;
protected $sms_notif;
/**
* Make notification object
*
@ -33,6 +36,12 @@ class NotificationHelper
return $this;
}
public function makeSmsNotif($template_name, $options = [])
{
$this->sms_notif = __('notification.sms.templates.'.$template_name, $options);
return $this;
}
/**
* Fetch message from notifications lang file
*
@ -56,7 +65,7 @@ class NotificationHelper
public function sendNotifications($users, $level = null) {
switch ($level) {
case "emergency":
// Notification::send($users, new SmsNotification($notif));
Notification::send($users, new SmsNotification($this->sms_notif, enum('sms.types.ultra_fast_send.name')));
case "critical":
Notification::send($users, new MailNotification($this->notif));
case "high":
@ -64,10 +73,10 @@ class NotificationHelper
case "medium":
Notification::send($users, new FcmNotification($this->notif));
case "low":
Notification::send($users, new SocketNotification($this->notif));
// Notification::send($users, new SocketNotification($this->notif));
break;
default:
Notification::send($users, new SocketNotification($this->notif));
// Notification::send($users, new SocketNotification($this->notif));
}
}

16
config/smsirlaravel.php

@ -0,0 +1,16 @@
<?php
return [
/* Important Settings */
// ======================================================================
'webservice-url' => env('SMSIR_WEBSERVICE_URL','https://ws.sms.ir/'),
// SMS.ir Api Key
'api-key' => env('SMSIR_API_KEY',null),
// SMS.ir Secret Key
'secret-key' => env('SMSIR_SECRET_KEY',null),
// Your sms.ir line number
'line-number' => env('SMSIR_LINE_NUMBER',null),
// ======================================================================
];

1
database/migrations/2020_08_18_085016_create_users_table.php

@ -21,6 +21,7 @@ class CreateUsersTable extends Migration
$table->string('username')->unique();
$table->string('password');
$table->boolean('active')->default(false);
$table->boolean('has_password')->default(false);
$table->boolean('has_avatar')->default(false);
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->useCurrent();

1
database/migrations/2021_03_08_114700_create_notifications_table.php

@ -17,7 +17,6 @@ class CreateNotificationsTable extends Migration
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->unsignedBigInteger('business_id');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();

19
docker-compose.yml

@ -73,6 +73,7 @@ services:
depends_on:
- mysql
mysql:
user: root
image: 'mysql:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
@ -83,7 +84,9 @@ services:
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
- 'sailmysql:/var/lib/mysql'
# - 'sailmysql:/var/lib/mysql'
- ./my.cnf:/etc/mysql/conf.d/my.cnf
- ./storage/logs/mysql:/var/lib/mysql
networks:
- hi-user
healthcheck:
@ -144,6 +147,20 @@ services:
- 9000:9000
- 12201:12201
- 1514:1514
- 5044:5044
networks:
- hi-user
filebeat:
restart: always
depends_on:
- graylog
user: root
container_name: filebeat
image: docker.elastic.co/beats/filebeat:7.11.2
volumes:
- ./storage/logs/mysql:/var/log/mysql
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml
- ./filebeat.reference.yml:/usr/share/filebeat/filebeat.reference.yml
networks:
- hi-user
networks:

2521
filebeat.reference.yml
File diff suppressed because it is too large
View File

232
filebeat.yml

@ -0,0 +1,232 @@
filebeat.config:
modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
processors:
- add_cloud_metadata: ~
- add_docker_metadata: ~
filebeat.inputs:
#------------------------------ Log input --------------------------------
- type: log
# Change to true to enable this input configuration.
enabled: true
# Paths that should be crawled and fetched. Glob based paths.
# To fetch all ".log" files from a specific level of subdirectories
# /var/log/*/*.log can be used.
# For each file found under this path, a harvester is started.
# Make sure not file is defined twice as this can lead to unexpected behaviour.
paths:
- /var/log/mysql/*.log
#- c:\programdata\elasticsearch\logs\*
# Configure the file encoding for reading files with international characters
# following the W3C recommendation for HTML5 (http://www.w3.org/TR/encoding).
# Some sample encodings:
# plain, utf-8, utf-16be-bom, utf-16be, utf-16le, big5, gb18030, gbk,
# hz-gb-2312, euc-kr, euc-jp, iso-2022-jp, shift-jis, ...
#encoding: plain
# Exclude lines. A list of regular expressions to match. It drops the lines that are
# matching any regular expression from the list. The include_lines is called before
# exclude_lines. By default, no lines are dropped.
#exclude_lines: ['^DBG']
# Include lines. A list of regular expressions to match. It exports the lines that are
# matching any regular expression from the list. The include_lines is called before
# exclude_lines. By default, all the lines are exported.
#include_lines: ['^ERR', '^WARN']
# Exclude files. A list of regular expressions to match. Filebeat drops the files that
# are matching any regular expression from the list. By default, no files are dropped.
#exclude_files: ['.gz$']
# Method to determine if two files are the same or not. By default
# the Beat considers two files the same if their inode and device id are the same.
#file_identity.native: ~
# Optional additional fields. These fields can be freely picked
# to add additional information to the crawled log files for filtering
#fields:
# level: debug
# review: 1
# Set to true to store the additional fields as top level fields instead
# of under the "fields" sub-dictionary. In case of name conflicts with the
# fields added by Filebeat itself, the custom fields overwrite the default
# fields.
#fields_under_root: false
# Set to true to publish fields with null values in events.
#keep_null: false
# By default, all events contain `host.name`. This option can be set to true
# to disable the addition of this field to all events. The default value is
# false.
#publisher_pipeline.disable_host: false
# Ignore files which were modified more then the defined timespan in the past.
# ignore_older is disabled by default, so no files are ignored by setting it to 0.
# Time strings like 2h (2 hours), 5m (5 minutes) can be used.
#ignore_older: 0
# How often the input checks for new files in the paths that are specified
# for harvesting. Specify 1s to scan the directory as frequently as possible
# without causing Filebeat to scan too frequently. Default: 10s.
#scan_frequency: 10s
# Defines the buffer size every harvester uses when fetching the file
#harvester_buffer_size: 16384
# Maximum number of bytes a single log event can have
# All bytes after max_bytes are discarded and not sent. The default is 10MB.
# This is especially useful for multiline log messages which can get large.
#max_bytes: 10485760
# Characters which separate the lines. Valid values: auto, line_feed, vertical_tab, form_feed,
# carriage_return, carriage_return_line_feed, next_line, line_separator, paragraph_separator.
#line_terminator: auto
### Recursive glob configuration
# Expand "**" patterns into regular glob patterns.
#recursive_glob.enabled: true
### JSON configuration
# Decode JSON options. Enable this if your logs are structured in JSON.
# JSON key on which to apply the line filtering and multiline settings. This key
# must be top level and its value must be string, otherwise it is ignored. If
# no text key is defined, the line filtering and multiline features cannot be used.
#json.message_key:
# By default, the decoded JSON is placed under a "json" key in the output document.
# If you enable this setting, the keys are copied top level in the output document.
#json.keys_under_root: false
# If keys_under_root and this setting are enabled, then the values from the decoded
# JSON object overwrite the fields that Filebeat normally adds (type, source, offset, etc.)
# in case of conflicts.
#json.overwrite_keys: false
# If this setting is enabled, then keys in the decoded JSON object will be recursively
# de-dotted, and expanded into a hierarchical object structure.
# For example, `{"a.b.c": 123}` would be expanded into `{"a":{"b":{"c":123}}}`.
#json.expand_keys: false
# If this setting is enabled, Filebeat adds a "error.message" and "error.key: json" key in case of JSON
# unmarshaling errors or when a text key is defined in the configuration but cannot
# be used.
#json.add_error_key: false
### Multiline options
# Multiline can be used for log messages spanning multiple lines. This is common
# for Java Stack Traces or C-Line Continuation
# The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
# multiline.pattern: ^\[
# Defines if the pattern set under pattern should be negated or not. Default is false.
#multiline.negate: false
# Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
# that was (not) matched before or after or as long as a pattern is not matched based on negate.
# Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
#multiline.match: after
# The maximum number of lines that are combined to one event.
# In case there are more the max_lines the additional lines are discarded.
# Default is 500
#multiline.max_lines: 500
# After the defined timeout, an multiline event is sent even if no new pattern was found to start a new event
# Default is 5s.
#multiline.timeout: 5s
# To aggregate constant number of lines into a single event use the count mode of multiline.
#multiline.type: count
# The number of lines to aggregate into a single event.
#multiline.count_lines: 3
# Do not add new line character when concatenating lines.
#multiline.skip_newline: false
# Setting tail_files to true means filebeat starts reading new files at the end
# instead of the beginning. If this is used in combination with log rotation
# this can mean that the first entries of a new file are skipped.
#tail_files: false
# The Ingest Node pipeline ID associated with this input. If this is set, it
# overwrites the pipeline option from the Elasticsearch output.
#pipeline:
# If symlinks is enabled, symlinks are opened and harvested. The harvester is opening the
# original for harvesting but will report the symlink name as source.
#symlinks: false
# Backoff values define how aggressively filebeat crawls new files for updates
# The default values can be used in most cases. Backoff defines how long it is waited
# to check a file again after EOF is reached. Default is 1s which means the file
# is checked every second if new lines were added. This leads to a near real time crawling.
# Every time a new line appears, backoff is reset to the initial value.
#backoff: 1s
# Max backoff defines what the maximum backoff time is. After having backed off multiple times
# from checking the files, the waiting time will never exceed max_backoff independent of the
# backoff factor. Having it set to 10s means in the worst case a new line can be added to a log
# file after having backed off multiple times, it takes a maximum of 10s to read the new line
#max_backoff: 10s
# The backoff factor defines how fast the algorithm backs off. The bigger the backoff factor,
# the faster the max_backoff value is reached. If this value is set to 1, no backoff will happen.
# The backoff value will be multiplied each time with the backoff_factor until max_backoff is reached
#backoff_factor: 2
# Max number of harvesters that are started in parallel.
# Default is 0 which means unlimited
#harvester_limit: 0
### Harvester closing options
# Close inactive closes the file handler after the predefined period.
# The period starts when the last line of the file was, not the file ModTime.
# Time strings like 2h (2 hours), 5m (5 minutes) can be used.
#close_inactive: 5m
# Close renamed closes a file handler when the file is renamed or rotated.
# Note: Potential data loss. Make sure to read and understand the docs for this option.
#close_renamed: false
# When enabling this option, a file handler is closed immediately in case a file can't be found
# any more. In case the file shows up again later, harvesting will continue at the last known position
# after scan_frequency.
#close_removed: true
# Closes the file handler as soon as the harvesters reaches the end of the file.
# By default this option is disabled.
# Note: Potential data loss. Make sure to read and understand the docs for this option.
#close_eof: false
### State options
# Files for the modification data is older then clean_inactive the state from the registry is removed
# By default this is disabled.
#clean_inactive: 0
# Removes the state for file which cannot be found on disk anymore immediately
#clean_removed: true
# Close timeout closes the harvester after the predefined time.
# This is independent if the harvester did finish reading the file or not.
# By default this option is disabled.
# Note: Potential data loss. Make sure to read and understand the docs for this option.
#close_timeout: 0
output.logstash:
hosts: ["graylog:5044"]

19
my.cnf

@ -0,0 +1,19 @@
[mysqld]
sync_binlog = 1
innodb_buffer_pool_size = 1G
innodb_log_file_size = 2047M
innodb_flush_log_at_trx_commit = 0
innodb_flush_method = O_DIRECT
innodb_buffer_pool_instances = 8
innodb_thread_concurrency = 8
innodb_io_capacity = 1000
innodb_io_capacity_max = 3000
innodb_buffer_pool_dump_pct = 75
innodb_adaptive_hash_index_parts = 16
innodb_read_io_threads = 16
innodb_write_io_threads = 16
innodb_flush_neighbors = 0
innodb_flushing_avg_loops = 100
innodb_page_cleaners = 8
long_query_time = 0.2
slow_query_log = ON

22
resources/lang/fa/notification.php

@ -85,4 +85,26 @@ return [
'suspended' => 'حساب مسدود شد.',
],
'auth' => [
'verification' => [
'greeting' => 'سلام کاربر گرامی!',
'subject' => 'لینک احراز هویت',
'body' => 'کد تایید شما :code',
'new_body' => 'برای ادامه فرایند ثبت نام لینک زیر را دنبال کنید.',
'link' => 'http://localhost:3000/auth/verification?email=:email&type=:type&signature=:signature'
]
],
'sms' => [
'templates' => [
'template_name' => [
'template_id' => 'asdasd',
'params' => [
'user' => ':user',
'business' => ':business',
]
]
]
]
];

10
routes/api.php

@ -6,10 +6,6 @@ $router->get('/lab', function () {
throw new \Exception("^_^");
});
$router->get('/ntest', function () {
$user = \App\Models\User::find(1);
\Illuminate\Support\Facades\Notification::send($user, new \App\Notifications\SocketNotification(['title' => "hello!!!", 'body' => 'sss']));
})->middleware('bindBusiness');
$router->group(['prefix' => 'actions'], function () use ($router) {
$router->group(['prefix' => 'businesses'], function () use ($router) {
$router->group(['prefix' => '{business}', 'middleware' => 'bindBusiness'], function () use ($router) {
@ -23,6 +19,7 @@ $router->get('/{transaction}/redirection', 'CreditController@redirection');
$router->group(['prefix' => 'auth'], function () use ($router) {
$router->get('/', 'AuthController@auth')->middleware('auth:api');
$router->post('/checking', 'AuthController@emailChecking');
$router->delete('/', 'AuthController@delete');
$router->get('/info', 'AuthController@authWithInfo')->middleware('auth:api');
$router->post('login', 'AuthController@login');
@ -33,7 +30,10 @@ $router->group(['prefix' => 'auth'], function () use ($router) {
$router->post('forget-password', 'AuthController@forgetPassword');
$router->post('update-password', 'AuthController@updatePassword');
$router->post('verification', 'AuthController@verification');
$router->post('verification', 'AuthController@verification')->name('verification');
$router->post('resend', 'AuthController@resendLink')->middleware('throttle:1'); // one request per min
$router->post('link-verification', 'AuthController@linkVerification');
$router->get('google/redirect', 'AuthController@redirectToGoogle')->name('google.redirect');
$router->get('google/callback', 'AuthController@handleGoogleCallback')->name('google.callback');

Loading…
Cancel
Save