Laravel notification facade check user before sending notification - laravel

I have a situation that I am sending a notification to multiple users and in past i have used this code:
foreach ($users as $user) {
$user->notify(new StaffNotify($dirtyAttributes, $user));
}
and I would check inside that notification if a user has a player_id
public function via($notifiable)
{
if ($this->user->player_id) {
return [OneSignalChannel::class, 'mail'];
} else {
return ['mail'];
}
}
(for OneSignal) and if he has I would send a push notification also on their mobile phone.
But with this new code:
\Notification::send($users, new StaffNotify($dirtyAttributes));
It is much better because i have only 1 request on my server instead of 250. I don't know how to check if a user has player_id because this works differently.
Does anyone know how to check the user before sending the notification?

You don't have to pass the user as an argument, you already have it in $notifiable and you can check what ever you want.
public function via($notifiable)
{
if ($notifiable->player_id) {
return [OneSignalChannel::class, 'mail'];
} else {
return ['mail'];
}
}

Related

Laravel: How to properly send notification to multiple users with different url/ in Registration Controller

I am trying to send notifications to the user after registration with the default auth of laravel 6. I tried using the notification facade, but it gives the same URL for all emails. I tried using the ->notify, but it gives me the error. By the way, I am using smtp.office365.com
Expected response code 354 but got code "503", with the message "503 5.5.1 Bad sequence of commands"
RegisterController
$users = User::where('department_id', $data['department'])
->where(function ($query) {
$query->where('manager_level', 'sm1')
->orWhere('manager_level', 'sm2')
->orWhere('department_level', 'dh');
})->get();
foreach ($users as $u) {
$u->notify(new ConfirmUser(($u->department_level == 'dh') ? $u->department_level : $u->manager_level));
}
}
InConfirmUser
protected $level;
/**
* Create a new notification instance.
*/
public function __construct($level)
{
$this->level = $level;
}
/**
* Get the notification's delivery channels.
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Confirm User')
->greeting("Dear {$notifiable->name},")
->line('Please click the button below to confirm that the concerned user is your staff')
->action('Confirm User', url('/').$this->level)
->line('If you did not know the concerend staff, no further action is required.');
}
/**
* Get the array representation of the notification.
*/
public function toArray($notifiable)
{
return [
//
];
}
To fix the error message change the value of MAIL_ENCRYPTION to tls in your configuration file and also make sure the MAIL_FROM_ADDRESS is exactly your Office365 email adress.
Tell us more about the ->department property. Isn't it possible that the department can be the same? Or is it null in the mail?
In the code you've wrote $u->deparment without a t. So change it to this:
u->notify(new ConfirmUser($u->department));
This could eventually solve your problem.

Laravel - Use Middleware for restrict the user blocked profile?

I am working on a project in Laravel, it's a scratch of a social network. I have a important question. I have a controller with the profile of the user, there first of all i look if the user exists, if not exists it gets a 404, if exists get the data and the controller sends it to the view.
But now I was thinking that I can do a Blocking System between users. I created the table with N:n, were it's the user id who blocked and the user id of the destiny.
I made a Middleware to check if the users is blocked. But this middleware is executed on the route, and this means is executed before check if the user exists. It doesnt make me any problem. It's all working but my question is: This the best solution? It's better use a policy?
User Model
public function usersBlockedI()
{
return $this->belongsToMany(User::class, 'u_blocks', 'u_from', 'u_to');
}
u_blocks is the name of the table.
u_from is the name of the user who made the block.
u_to the user who received the block.
User Controller:
public function showProfile($username)
{
$profileUser = User::where('username', $username)->first();
if ($profileUser == null) {
return abort(404);
} else {
$profileUser['dataUser'] = dataUser::where('u_id', $profileUser['id'])->first();
return view('user.profile', compact('profileUser'));
}
}
Block Middleware
public function handle($request, Closure $next)
{
if (auth()->user()) {
$usernameProfile = $request->route('username');;
$ActualuserID = auth()->user()->id;
$checkIfBlocked = User::where('username', $usernameProfile)->first()->usersBlockedI->where('id', $ActualuserID);
if (!sizeof($checkIfBlocked) == 0) {
abort(404);
}
}
return $next($request);
}
Routes
Route::group(['middleware' => 'blocked'], function () {
Route::get('/{username}', 'UserController#showProfile')->name('showProfile');
});

How to Verify Email Without Asking the User to Login to Laravel

I am developing a Laravel application. My application is using Laravel built-in auth feature. In the Laravel auth when a user registers, a verification email is sent. When a user verifies the email click on the link inside the email, the user has to login again to confirm the email if the user is not already logged in.
VerificationController
class VerificationController extends Controller
{
use VerifiesEmails, RedirectsUsersBasedOnRoles;
/**
* Create a new controller instance.
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
public function redirectPath()
{
return $this->getRedirectTo(Auth::guard()->user());
}
}
I tried commenting on this line.
$this->middleware('auth');
But it's s not working and instead, throwing an error. How can I enable Laravel to be able to verify email even if the user is not logged in?
First, remove the line $this->middleware('auth');, like you did.
Next, copy the verify method from the VerifiesEmails trait to your VerificationController and change it up a bit. The method should look like this:
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
This overrides the method in the VerifiesUsers trait and removes the authorization check.
Security (correct me if I'm wrong!)
It's still secure, as the request is signed and verified. Someone could verify another user's email address if they somehow gain access to the verification email, but in 99% of cases this is hardly a risk at all.
Here's a more future proof solution to the problem:
class VerificationController extends Controller
{
// …
use VerifiesEmails {
verify as originalVerify;
}
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth'); // DON'T REMOVE THIS
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Mark the authenticated user's email address as verified.
*
* #param Request $request
* #return Response
*
* #throws AuthorizationException
*/
public function verify(Request $request)
{
$request->setUserResolver(function () use ($request) {
return User::findOrFail($request->route('id'));
});
return $this->originalVerify($request);
}
}
So when an email confirmation link is clicked by an unauthenticated user the following will happen:
User will be redirected to the login view 1
User enters credentials; logs in successfully 2
User will be redirect back to the email confirmation URL
Email will be marked as confirmed
1 The email will not be marked as confirmed at this point.
2 The user may enter bad credentials multiple times. As soon as he enters the correct credentials he will be redirected to the intended email confirmation URL.
// For Laravel 6 and Above
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
use App\User;
// comment auth middleware
//$this->middleware('auth');
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect($this->redirectPath())->with('verified', true);
}
Solution to allow email verification for users who are not logged in (i.e. without auth):
Changes to: app/Http/Controllers/Auth/VerificationController.php:
$this->middleware('auth'); to $this->middleware('auth')->except('verify');
Copy verify() method from the VerifiesEmails trait.
Edit verify method to work without expected $request->user() data.
My verify() method in the VerificationController looks like this:
public function verify(\Illuminate\Http\Request $request)
{
$user = User::find($request->route('id'));
if ($request->route('id') != $user->getKey()) {
throw new AuthorizationException;
}
if ($user->markEmailAsVerified())
event(new Verified($user));
return redirect()->route('login')->with('verified', true);
}
Signed middleware
Laravel uses a middleware named signed to check the integrity of URLs that were generated by the application. Signed checks whether the URL has been changed since it was created. Try changing the id, expiry time or the signature in the url and it will lead to an error - very effective and useful middleware to protect the verify() method
For more information: https://laravel.com/docs/8.x/urls#signed-urls
(Optional)
I redirected my users to the login route, rather than the intended route for two reasons. 1) After login, it would try to redirect the user to the email verification link, leading to an error; 2) I wanted to use the verified true flash data that was attached to the redirect, to show an alert on the login page, if the user had successfully verified their email address.
Example of my login page alert:
#if(session()->has('verified'))
<div class="alert alert-success">Your email address has been successfully verified.</div>
#endif
Suggestions
If you have any suggestions on how I could improve this code, please let me know. I'd be happy to edit this answer.
You should not remove $this->middleware('auth') altogether as that will effect the redirects. If you remove it, the unauthenticated users will be redirected to "/email/verify" instead of "/login"
so $this->middleware('auth'); will be changed to $this->middleware('auth')->except('verify'); in "VerificationController"
Also copy the "verify" function from "VerifiesEmails" into "VerificationController"
add these two lines of code at the top of the function
$user = User::find($request->route('id'));
auth()->login($user);
so you are logging in the user programmatically and then performing further actions
Here's my take on the situation. Verification requires user to login before it can complete the verification, so we can override the verify function and login user using ID we received in the link. It is safe cause verify function is not called if Laravel can't verify the signature from URL so even if someone temper the URL they won't be able to bypass it.
Go to your VerificationController and add the following function at the end of the file.
public function verify(Request $request)
{
if (!auth()->check()) {
auth()->loginUsingId($request->route('id'));
}
if ($request->route('id') != $request->user()->getKey()) {
throw new AuthorizationException;
}
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath());
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('verified', true);
}
Note
Make sure you have same_site value in 'config/session.php' set to 'lax'. If it is set to 'strict' then it won't persist session if you were redirected from another site. For example, if you click a verification link from Gmail then your session cookie won't persist, so it won't redirect you to dashboard, but it sets 'email_verified_at' field in the database marking the verification successful. The user won't get any idea what was happened because it will redirect the user to the login page. When you have set it to 'strict', it will work if you copy the verification link directly in the browser address bar but not if the user clicks the link from the Gmail web client because it uses redirect to track the link.
if you want to active user account without login you can do that in 2 steps
1- Remove or comment Auth middleware in VerificationController
Example below:
public function __construct()
{
//$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
2- since verify route passing the {id} you can just edit verify function to find the user by the route id request like code below :
file path : *:\yourproject\vendor\laravel\framework\src\Illuminate\Foundation\Auth\VerifiesEmails.php
$user = User::findOrfail($request->route('id'));
Complete example
public function verify(Request $request)
{
$user = User::findOrfail($request->route('id'));
if (! hash_equals((string) $request->route('id'), (string) $user->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($user->hasVerifiedEmail()) {
return redirect($this->redirectPath())->with('verified', true);
}
if ($user->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect($this->redirectPath())->with('registered', true);
}
I change EmailVerificationRequest but i now this is wrong, any way it's work.
Warning
This change on the vendor
protected $user;
public function authorize()
{
$this->user = \App\Models\User::find($this->route('id'));
if ($this->user != null){
if (! hash_equals((string) $this->route('id'),
(string) $this->user->getKey())) {
return false;
}
if (! hash_equals((string) $this->route('hash'),
sha1($this->user->getEmailForVerification()))) {
return false;
}
return true;
}
return false;
}
To use inner laravel logic (without overriding the logic), we simply create $request->user() and call trait's verify method. And manually sign in the user when the verification is successful.
use VerifiesEmails {
verify as parentVerify;
}
public function verify(Request $request)
{
$user = User::find($request->route('id'));
if (!$user) return abort(404);
$request->setUserResolver(function () use($user) {
return $user;
});
return $this->parentVerify($request);
}
public function verified(Request $request)
{
Auth::login($request->user());
}

Laravel Notifications - delay email sending and cancel if condition met

I have an app where I am sending a push notification which is fine if the user is logged into the application - however, if they're not / if they have not read the notification within X minutes I'd like to send them an email.
The way I am going about this is to use Laravel Notifications to create a mail, broadcast & database notification. On the toMail() method I'm returning a mailable with a delay -
public function toMail($notifiable)
{
return (new \App\Mail\Order\NewOrder($this->order))
->delay(now()->addMinutes(10));
}
After the minutes are up, the email will send but, before the send goes ahead I'd like to perform a check to see if the push/database notification has already been marked as read and if it has cancel the email send. The only way I can think to do this is to bind to the MessageSending event that is baked into Laravel -
// listen for emails being sent
'Illuminate\Mail\Events\MessageSending' => [
'App\Listeners\Notification\SendingEmail'
],
The only problem is this listener receives a Swift mail event and not the original mailable I was dispatching so I don't know how to cancel it. Any ideas and thanks in advance?
Class extends Notification
public function via($notifiable)
{
if($this->dontSend($notifiable)) {
return [];
}
return ['mail'];
}
public function dontSend($notifiable)
{
return $this->appointment->status === 'cancelled';
}
Class EventServiceProvider
protected $listen = [
NotificationSending::class => [
NotificationSendingListener::class,
],
];
Class NotificationSendingListener
public function handle(NotificationSending $event)
{
if (method_exists($event->notification, 'dontSend')) {
return !$event->notification->dontSend($event->notifiable);
}
return true;
}
For more details look article Handling delayed notifications in Laravel

Send message to multiple users in Protected Channel

I have the following code that contains info about one user to send read time message.
Question: Is there any way to send message to more 10 users my current code sends message to one user like this
return new PrivateChannel('SendMessageChannel.1');
Event Class
class SendMessageEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $Message;
public function __construct($message)
{
$this->Message = $message;
}
public function broadcastOn()
{
return new PrivateChannel('SendMessageChannel.1');
}
}
If I'm not wrong you are trying to create a group chat where multiple users can chat with each other in a single chat room.
What you want is not just up to the Echo implementation. It also requires database structure accordingly.
So I can give you the brief idea about how I did previously.
I have a chatrooms table which contains ids(in comma separated form) of all the users which are added to that room. In channel route, this how I'm checking that a specific user should allow to read a message or not:
Broadcast::channel('private-chat-room-{chatRoom}', function ($user, $chatRoom) {
$chatRoom = App\Models\ChatRoom::find($chatRoom);
if(in_array(auth()->user()->id, explode(',', $chatRoom->user_ids))) {
return true;
} else {
return false;
}
});

Resources