Verification registration by email using laravel - laravel

Hi everyone on this day I am designing my website and any new user must register on that and I used email to verification from them.
I am using laravel on my website I want to use the real email to send verification code for the new users I don't want a mail-trap service I want the real email. please tell me what I need to complete this job.
Remember that I want to do this when my website on free hosting, not real hosting.

You can implement the email through the event or in the same controller. My solution has been tested on Laravel 5.6. First of all, make the email settings for gmail in the .env file.
MAIL_DRIVER=smtp
MAIL_HOST=smtp.googlemail.com
MAIL_PORT=465
MAIL_USERNAME=real email
MAIL_PASSWORD=real password
MAIL_ENCRYPTION=ssl
Suppose you want the users to have to activate their account after registration. So create a new model:
php artisan make:modal VerifyUser –m
Add this code to created migration:
public function up()
{
Schema::create('verify_users', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->string('token')->index();
$table->timestamps();
});
}
Then add verified field to the users table:
$table->boolean('verified')->default(false);
Then:
php artisan migrate
Add this method to User Model:
class User extends Authenticatable
{
public function verifyUser()
{
return $this->hasOne('App\VerifyUser');
}
}
Add this method to VerifyUser Model:
class VerifyUser extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
}
Create a class for email that inherits from Maiable. This file is created in the email folder.
php artisan make:mail VerifyMail
Apply the following changes to VerifyMail class:
namespace App\Mail;
...
class VerifyMail extends Mailable
{
use Queueable, SerializesModels;
public $user;
public function __construct($user)
{
$this->user = $user;
}
public function build()
{
return $this->view('emails.verifyUser');
}
}
Create a folder named emails and make a blade file named verifyUser in it. And Put the following code in it:
<body>
<h2>Welcome to the site {{$user['name']}}</h2>
<br/>
Your registered email-id is {{$user['email']}} , Please click on the below link to verify your email account
<br/>
Verify Email
</body>
Change create method in RegisterController:
use App\Mail\VerifyMail;
use App\VerifyUser;
use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;
...
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
$verifyUser = VerifyUser::create([
'user_id' => $user->id,
'token' => sha1(time())
]);
\Mail::to($user->email)->send(new VerifyMail($user));
return $user;
}
With this method, after the user register, a record is added to the user table, but the verified value is currently false or zero. Now to prevent the user login in immediately after registration, add the following method to the RegisterController:
protected function registered(Request $request, $user)
{
$this->guard()->logout();
return redirect('/login')->with('status', 'We sent you an activation code. Check your email and click on the link to verify.');
}
Unverified user should not log in in any way. And you have to override authenticated method to successful user login. So add authenticated method into LoginController:
public function authenticated(Request $request, $user)
{
if (!$user->verified) {
auth()->logout();
return back()->with('warning', 'You need to confirm your account. We have sent you an activation code, please check your email.');
}
return redirect()->intended($this->redirectPath());
}
Add this code to login.blade.php to display the message above:
#if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
#endif
#if (session('warning'))
<div class="alert alert-warning">
{{ session('warning') }}
</div>
#endif
Next, define a route for the link you sent to the user's email:
Route::get('/user/verify/{token}', 'Auth\RegisterController#verifyUser');
Now it's time to activate the user. Therefore, the verified value must be set to true or one. Add the following method to RegisterController:
public function verifyUser($token)
{
$verifyUser = VerifyUser::where('token', $token)->first();
if(isset($verifyUser) ){
$user = $verifyUser->user;
if(!$user->verified) {
$verifyUser->user->verified = 1;
$verifyUser->user->save();
$status = "Your e-mail is verified. You can now login.";
} else {
$status = "Your e-mail is already verified. You can now login.";
}
} else {
return redirect('/login')->with('warning', "Sorry your email cannot be identified.");
}
return redirect('/login')->with('status', $status);
}
Complete.

Related

Login and verify user - Call to a member function getKey() on null

I am trying to create a custom verification flow, where as soon as a user clicks the verification link, it logs him in and also verifies him, instead of first making him log in and only then the verification link works.
I built a custom notification URL in my CustomVerificationNotification, including the registered user_id, to login him later:
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.custom-verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
'user_id' => $this->user->id
]
);
}
Then in my web.php I added this route:
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')->name('verification.custom-verify');
Then in my CustomVerifyController:
public function login_and_verify(EmailVerificationRequest $request)
{
//..
}
But I get Call to a member function getKey() on null. And I can't edit EmailVerificationRequest, so what can I do? Is it possible to somehow call Auth::login($user); before calling the EmailVerificationRequest? (Because I have the user_id from the route)
I tried to follow the best answer from this post as well: How to Verify Email Without Asking the User to Login to Laravel
But I'm not sure then how to trigger the verify() method from the web.php and send the $request when I'm first calling the verify_and_login method
First you need verify that the URL is signed by adding the middleware signed
You don't want that anoyone having the url /email/verify/{id}/{hash}/{user_id} able to access this ressource without the signature.
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed')
->name('verification.custom-verify');
Then you need to verify that the hash correspond the user_id and for that you can use a Request or a Middleware. I think the Request fits better since Laravel already uses a Request for this.
CustomEmailVerificationRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Http\FormRequest;
class EmailVerificationRequest extends FormRequest
{
public function authorize()
{
$user = User::findOrFail($this->route('id'));
if (! hash_equals((string) $this->route('hash'), sha1($user->getEmailForVerification()))) {
return false;
}
return true;
}
}
Finally you need to login with the user and set is email as verified
CustomVerifyController.php
public function login_and_verify(CustomEmailVerificationRequest $request)
{
$user = User::findOrFail($this->route('id'));
Auth::login($user);
$user->markEmailAsVerified();
event(new Verified($user));
...
}
[Edit to add addition feature from comments]
In order to have a middleware that verify the signed URL and resend automatically the verification email, you need to build a custom middleware.
ValidateSignatureAndResendEmailVerification.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Routing\Exceptions\InvalidSignatureException;
use URL;
class ValidateSignatureAndResendEmailVerification
{
public function handle($request, Closure $next, $relative = null)
{
if(! URL::hasCorrectSignature($request, $relative !== 'relative')( {
throw new InvalidSignatureException;
}
if (URL::signatureHasNotExpired()) {
return $next($request);
}
return redirect()->route('resend-email-confirmation');
}
}
Then you need to add the middleware to Kernel.php
Kernel.php
protected $routeMiddleware = [
...
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'signed.email' => \App\Http\Middleware\ValidateSignatureAndResendEmailVerification::class,
...
];
Then, don't forget to update your route with the new middleware
web.php
Route::get('/email/verify/{id}/{hash}/{user_id}','Auth\CustomVerifyController#login_and_verify')
->middleware('signed.email')
->name('verification.custom-verify');

Laravel error when sending email notification

Laravel Version: 8.78.1
PHP Version: 8.0.10
I've created a custom command to run on a schedule and email a notification.
My Command class handle method:
public function handle()
{
$sql = "SELECT * FROM Licences WHERE (Expired = 1)";
$list = DB::select($sql);
return (new NotifyExpiredLicences($list))->toMail('me#gmail.com');
}
My notification method:
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Clients with Expired Licences')
->markdown('vendor/notifications/expiredlicences',
['clients' => $this->list, 'toname' => 'Me']);
}
Whenever I test this by running it manually with php artisan email:expired-licences I get the following error Object of class Illuminate\Notifications\Messages\MailMessage could not be converted to int from my command class in the handle method.
However, the preview of my email works fine & displays as expected:
Route::get('/notification', function () {
return (new SendExpiredLicences())->handle();
});
If I remove the return statement from my handle() method, then although I get no errors, neither in my console or in storage\logs, also the preview stops working.
At this point I'm sure I've missed something important from the way this is supposed to be done, but after going through the Laravel docs and looking at online tutorials/examples, I've no idea what.
I've got everything working - though not entirely sure it's the "Laravel way".
If anyone's got suggestions for improving it - add a comment or new answer and I'll try it out.
Console\Kernel.php:
protected function schedule(Schedule $schedule)
{
$schedule->command('email:expired-licences')
->weekdays()
->at('08:30');
}
App\Console\Commands\SendExpiredLicences.php:
class SendExpiredLicences extends Command
{
protected $signature = 'email:expired-licences';
protected $description = 'Email a list of expired licences to Admin';
private $mail;
public function _construct()
{
$clients = DB::select("[Insert SQL here]");
$this->mail = (new NotifyExpiredLicences($clients))->toMail('admin#example.com');
parent::__construct();
}
public function handle()
{
Mail::to('admin#example.com')->send($this->mail);
return 0;
}
public function preview()
{
return $this->mail;
}
}
App\Notifications\NotifyExpiredLicences.php:
class NotifyExpiredLicences extends Notification
{
public function __construct(protected $clients)
{
}
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new Mailable($this->clients));
}
}
App\Mail\ExpiredLicences.php:
class ExpiredLicences extends Mailable
{
public function __construct(private $clients)
{
}
public function build()
{
return $this
->subject('Clients with Expired Licences')
->markdown('emails/expiredlicences',
['clients' => $this->clients, 'toname' => 'Admin']);
}
}
resources\views\emails\expiredlicences.blade.php:
#component('mail::message')
# Hi {!! $toname !!},
#component('mail::table')
| Client | Expired |
| ------------- | --------:|
#foreach ($clients as $client)
|{!! $client->CompanyName !!} | {!! $client->Expired !!}|
#endforeach
#endcomponent
<hr />
Thanks, {!! config('app.name') !!}
#endcomponent
For previewing with the browser routes\web.php:
Route::get('/notification', function () {
return (new SendExpiredLicences())->preview();
});
Ok just to save more commenting, here's what I'd recommend doing. This is all based on the Laravel docs, but there are multiple ways of doing it, including what you've used above. I don't really think of them as "right and wrong," more "common and uncommon."
Console\Kernel.php: I'd keep this mostly as-is, but pass the email to the command from a config file, rather than having it fixed in the command.
use App\Console\Commands\SendExpiredLicences;
…
protected function schedule(Schedule $schedule)
{
$recipient = config('myapp.expired.recipient');
$schedule->command(SendExpiredLicences::class, [$recipient])
->weekdays()
->at('08:30');
}
config/myapp.php:
<?php
return [
'expired' => [
'recipient' => 'admin#example.com',
],
];
App\Console\Commands\SendExpiredLicences.php: update the command to accept the email address as an argument, use on-demand notifications, and get rid of preview() method. Neither the command or the notification need to know about the client list, so don't build it yet.
<?php
namespace App\Console\Commands;
use App\Console\Command;
use App\Notifications\NotifyExpiredLicences;
use Illuminate\Support\Facade\Notification;
class SendExpiredLicences extends Command
{
protected $signature = 'email:expired-licences {recipient}';
protected $description = 'Email a list of expired licences to the given address';
public function handle()
{
$recip = $this->argument('recipient');
Notification::route('email', $recip)->notify(new NotifyExpiredLicences());
}
}
App\Notifications\NotifyExpiredLicences.php: the toMail() method should pass the notifiable object (i.e. the user getting notified) along, because the mailable will be responsible for adding the To address before the thing is sent.
<?php
namespace App\Notifications;
use App\Mail\ExpiredLicenses;
use Illuminate\Notifications\Notification;
class NotifyExpiredLicences extends Notification
{
public function via($notifiable)
{
return ['mail'];
}
public function toMail($notifiable)
{
return (new ExpiredLicenses($notifiable));
}
}
App\Mail\ExpiredLicences.php: since the mail message actually needs the list of clients, this is where we build it. We get the recipient here, either from the user's email or the anonymous object.
<?php
namespace App\Mail;
use App\Models\Client;
use Illuminate\Notifications\AnonymousNotifiable;
class ExpiredLicences extends Mailable
{
private $email;
public function __construct(private $notifiable)
{
// this allows the notification to be sent to normal users
// not just on-demand
$this->email = $notifiable instanceof AnonymousNotifiable
? $notifiable->routeNotificationFor('mail')
: $notifiable->email;
}
public function build()
{
// or whatever your object is
$clients = Client::whereHas('licenses', fn($q)=>$q->whereExpired(1));
return $this
->subject('Clients with Expired Licences')
->markdown(
'emails.expiredlicences',
['clients' => $clients, 'toname' => $this->notifiable->name ?? 'Admin']
)
->to($this->email);
}
}
For previewing with the browser routes\web.php:
Route::get('/notification', function () {
// create a dummy AnonymousNotifiable object for preview
$anon = Notification::route('email', 'no#example.com');
return (new ExpiredLicencesNotification())
->toMail($anon);
});

Laravel Nova how to overwrite the nova LoginController

My project requires a username rather than email. I had this working in Laravel 5.8 and Nova v2.1.0. After upgrading to L 6.x N 2.6.1 everything broke. So I started over with clean L 6.x and N 2.6.1 install.
Now I want to customize the login but I do not want to edit any Nova Package scripts as before.
I've added this code to nova/Http/Controllers/LoginController.php and all works as expected.
public function username()
{
return 'username';
}
When I add the code to App/Nova/Http/Controller/LoginController.php (a copy of the original) the login still requires an email address. Or is using the original file in nova.
this is what i do on my end
i override the App\Http\Controllers\Auth\LoginController.php from
class LoginController extends Controller
to
class LoginController extends \Laravel\Nova\Http\Controllers\LoginController
if you want to use username or email on the login page you have to add this.
this method will determine how the user input they credential
public function username()
{
$login = \request()->input("email");
$field = filter_var($login, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
\request()->merge([$field => $login]);
return $field;
}
because the user can login using email or username but the default login from nova only have 1 input box. have to add this to display if the user input wrong username or that username did not exist
protected function sendFailedLoginResponse(Request $request)
{
throw ValidationException::withMessages([
'email' => [trans('auth.failed')],
]);
}
on my controller i have add other method to determine if the user is admin or able to access the backend or if the user is still active.
protected function authenticated(Request $request, $user)
{
if($user->isSuperAdmin()) {
return redirect(config('nova.path'));
}
if($user->can('backend')) {
return redirect(config('nova.path'));
}
return redirect('/');
}
by adding method to check user is active i need to add this method to check if the user can login
private function activeUser($username)
{
$user = User::where($this->username(), $username)->first();
if($user) {
return $user->active;
}
return false;
}
public function login(Request $request)
{
$active = $this->activeUser($request->only($this->username()));
if(! $active) {
return $this->sendFailedLoginResponse($request);
}
return parent::login($request);
}
hope this helps

How to access controller via both admin guards and normal user guards without using roles

I need to download the form posted from the supervisor. I can access the page but not the form since I can't download it. It's giving me the ERR_INVALID_RESPONSE error, but the supervisor can download it easily.
Could it be something wrong with the middleware? I struggled a lot but still can't download, please if anyone might know the problem please help me.
Controller
class DutiesController extends Controller
{
public function assignSupervisor(Request $request, $id)
{
$assignS = new Duty;
$assignS->student_id = $id;
$assignS->user_id = $request->supervisor_id;
$assignS->student_name = $request->student_name;
$assignS->save();
return back();
}
public function assignInstructor(Request $request, $id)
{
$assignS = Duty::where('student_id', $id)->first();
$assignS->admin_id = $request->instructor_id;
$assignS->save();
return back();
}
public function duties($id)
{
$duty = Duty::where('student_id', $id)->orWhere('user_id', $id)->orWhere('admin_id', $id)->first();
return view('Duty.show', compact('duty'));
}
public function assign(Request $request, $id)
{
$assign = Duty::findOrfail($id);
if ($request->hasFile('duty')) {
$this->validate($request, [
'duty' => 'required|file|mimes:pdf,doc'
]);
$fileNameWithExt = $request->file('duty')->getClientOriginalName();
$fileName = pathinfo($fileNameWithExt, PATHINFO_FILENAME);
$extension = $request->file('duty')->getClientOriginalExtension();
$fileNameToStore = $fileName.'_'.time().'.'.$extension;
$path = $request->file('duty')->storeAs('public/duty', $fileNameToStore);
$assign->duty = $fileNameToStore;
}
$assign->marks = $request->marks;
$assign->save();
return back();
}
public function getduty($id) // my download function
{
$download = Duty::findOrfail($id);
return Storage::download("/public/duty/".$download->duty);
}
public function assignSupervisorInstructor()
{
$users = User::with('campus')->where('role_id', 4)->get();
$supervisors = User::with('campus')->where('role_id', 2)->get();
$instructors = Admin::where('role_id', 3)->get();
return view('Assigning.index', compact('users', 'supervisors', 'instructors'));
}
}
Routes
Route::group(['middleware' => 'auth:web,admin'], function () {
//Now this routes can be accessible by both admin as well as
Route::get('/duties/downloads/{id}', 'DutiesController#getduty');
Route::post('/duties/assign/{id}', 'DutiesController#assign');
Route::get('/duties/myduties/{id}', 'DutiesController#duties');
Route::get('/duties/mydutty/{id}', 'DuttyController#duties');
Route::post('/duties/{id}', 'DutiesController#assignSupervisor');
Route::get('/assign', 'DutiesController#assignSupervisorInstructor');
Route::post('/duties/inst/{id}', 'DutiesController#assignInstructor');
});
Blade
<td>
<a href="/duties/downloads/{{$duty->id}}">
<button class="btn btn-success"><i class="fa fa-download"></i> Dowload Document</button>
</a>
</td>
I hope this is what you mean but this is how I differentiate between user types via Middleware.
Basically creating custom middleware (I guess you can also do it via the built in functionality but I prefer this), for example:
1) The MiddleWare:
app/Http/Middleware/CheckUserType.php:
namespace App\Http\Middleware;
use Auth;
use Closure;
use App\Usergroup;
class CheckUserType
{
/**
* This middleware checks if the user is logged in as a specific type
*
* #var array
*/
public function handle($request, Closure $next, ...$userGroups)
{
if (in_array(UserGroup::where('id', Auth::user()->groupid)->first()->title, $userGroups)) {
return $next($request);
} else {
return response('Unauthorized...', 401);
}
}
}
In my case I have a usergroups table that links to the user->groupid so I use that Model to cross-reference the id to the group title I give in in my router.
But you can modify this obviously.
Also note that I do ...$userGroups so I can iterate over multiple user types if I want to in the router (see below).
2) Register in your kernel:
Then register in app/Http/Kernel.php:
add to the protected $routeMiddleware:
checkUserType' => CheckUserType::class
Make sure to include you custom middleware (use App\Http\Middleware\CheckUserType;)
3) Routes:
So then at last in my router I have for example the following:
/**
* #section Routes that require the user to be a Manager or an Administrator
*/
Route::group(['middleware' => 'checkUserType:Managers,Administrators'], function () {
//
});
or:
/**
* #section Routes that require the user to be an Administrator
*/
Route::group(['middleware' => 'check.usertype:Administrators'], function () {
// User Routes
Route::delete('/users/delete/{id}', 'UserController#deleteUser');
});

Laravel Policy - Wrong User transmitted from Controller

I registered a Policy to access User's Profiles (view, edit).
I either allow it if:
The Profile you are trying to view belongs to you
You have the Permission "edit any profile".
so this is my view()-Function in the Policy:
public function view(User $user)
{
debug('User in Controller: '.$user->id);
return (Auth::user()->id === $user->id) || Auth::user()->can('edit any profile');
}
This is method to show the profile view taken from the ProfileController:
public function show(User $user) {
debug('User in Controller: '.$user->id);
$this->authorize('view', $user);
return view('profile.index')->with([
"user" => $user,
]);
}
And finally the route:
Route::get('/profile/{user}', 'ProfileController#show')->name('profile');
Of course, the Policy has been registered in the AuthServiceProvider:
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
User::class => ProfilePolicy::class,
];
Basically the controller transmits the wrong user to the policy. Here are both messages from the respective debug()'s:
So I don't like to answer my own questions but I found out what the problem was. The first Parameter in a policy function is always an instance of the current user.
So you can simply have this in your policy:
public function view(User $currentUser, User $user)
{
return $currentUser->is($user) || $currentUser->can('edit any profile');
}
Laravel automatically sets $currentUser to Auth::user().

Resources