Tapping into email verification functionality.

I'm trying to make it so that an admin registers users for the app and sends an invite, rather than the user registering themselves. I added the MustVerifyEmail trait to my User model, and I'm calling $this->sendEmailVerificationNotification() method after creating the user. I'm getting the error: Route [verification.verify] not defined. Which I get, I need to add the route, but I'm unsure of where the route should point to? Or is it more of a configuration change I need to make, rather than adding it to my web.php route file?
Solution:
ok, figured it out. This was the solution in case anyone is trying to do this oddly specific thing in the future: ``` class VerificationResponse implements EmailVerificationResponse {...
Jump to solution
11 Replies
Jon Mason
Jon MasonOP13mo ago
I added this to my web.php file: Route::get('/email/verify/{id}/{hash}', [EmailVerificationController::class, '__invoke'])->middleware(['signed'])->name('verification.verify'); Which resolves the error, but I'm still not sure if I'm doing it right. I believe I should be calling the Filament EmailVerificationController. However, when I do that, I get Call to a member function hasVerifiedEmail() on null I think part of the problem might be that the EmailVerificationController expects the user to be logged in already? In my case, the user won't be able to log in until they reset their password, so I technically need to redirect them to the password reset route. In the EmailVerificationController class, it returns: return app(EmailVerificationResponse::class); It seems like if I create a class that implements that, and then do my redirect there, that should work. I'm not sure if I'm doing any of this correctly, so hoping someone might be able to point me in the right direction.
LeandroFerreira
LeandroFerreira13mo ago
$panel->emailVerification() ?
Jon Mason
Jon MasonOP13mo ago
yeah, I have that included. I think I've got it mostly working. I can send the email verification, and I can mark it as verified in the DB. What I need to do is validate the hash in the verification URL, as it doesn't seem like that's happening anywhere (which I find odd) unless it's happening somehow behind the scenes and I'm just not seeing it, and then I need to redirect to the password reset view that would be generated from a password reset email. Trying to skip the step where they put in their email address and it sends them an email. nvm, it's generating a signed route and i'm using the signed middleware, so all of that is taken care of. I just need to generate a new signed route for the password reset Still working on this...I'm unable to redirect to the password reset screen after the user verifies their email. I get the error: Route [filament.admin.auth.password-reset.reset] not defined. But my login/password reset/email verification is all defined on the app panel, not the admin panel. And when I run artisan route:list, the route that's shown is filament.app.auth.password-reset.reset. And the regular password reset is working as expected. Here's how I'm attempting to do it:
class VerificationResponse implements EmailVerificationResponse
{
public function toResponse($request)
{
$user = User::find($request->route('id'));
$token = Password::broker()->createToken($user);

$url = Filament::getResetPasswordUrl($token, $user); <--fails here.
Log::debug($url);

return redirect()->url($url);
}
}
class VerificationResponse implements EmailVerificationResponse
{
public function toResponse($request)
{
$user = User::find($request->route('id'));
$token = Password::broker()->createToken($user);

$url = Filament::getResetPasswordUrl($token, $user); <--fails here.
Log::debug($url);

return redirect()->url($url);
}
}
and the getResetPasswordUrl method in Filament is getting the currentPanel, which I don't understand.
/**
* @param array<mixed> $parameters
*/
public function getResetPasswordUrl(string $token, CanResetPassword | Model | Authenticatable $user, array $parameters = []): string
{
return $this->getCurrentPanel()->getResetPasswordUrl($token, $user, $parameters);
}
/**
* @param array<mixed> $parameters
*/
public function getResetPasswordUrl(string $token, CanResetPassword | Model | Authenticatable $user, array $parameters = []): string
{
return $this->getCurrentPanel()->getResetPasswordUrl($token, $user, $parameters);
}
Can I set the current panel for the purposes of what I'm trying to do? I don't understand why the current panel matters for a password reset.
Solution
Jon Mason
Jon Mason13mo ago
ok, figured it out. This was the solution in case anyone is trying to do this oddly specific thing in the future:
class VerificationResponse implements EmailVerificationResponse
{
public function toResponse($request)
{
$user = User::find($request->route('id'));
$token = Password::broker()->createToken($user);

$panel = Filament::getPanel('app');
Filament::setCurrentPanel($panel);

$url = Filament::getResetPasswordUrl($token, $user);

return redirect()->to($url);
}
}
class VerificationResponse implements EmailVerificationResponse
{
public function toResponse($request)
{
$user = User::find($request->route('id'));
$token = Password::broker()->createToken($user);

$panel = Filament::getPanel('app');
Filament::setCurrentPanel($panel);

$url = Filament::getResetPasswordUrl($token, $user);

return redirect()->to($url);
}
}
toufikkhattara
toufikkhattara11mo ago
@Jon Mason Your solutions almost works for me but I am just confused as to how to bind the custom VerificationResponse to act instead of the default EmailVerificationResponse. I tried to register it inside my service providers but It does not work. I suspect that are some different mechanisms in Filament
Jon Mason
Jon MasonOP11mo ago
@toufikkhattara This may be helpful for you. I have this controller:
namespace App\Http\Controllers\Auth;

use App\Models\User;
use App\Http\Responses\VerificationResponse;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Http\Responses\Auth\Contracts\EmailVerificationResponse;

class EmailVerificationController
{
public function __invoke(Request $request): EmailVerificationResponse
{
/** @var MustVerifyEmail $user */
$user = User::find($request->route('id'));

if ((!$user->hasVerifiedEmail()) && $user->markEmailAsVerified()) {
event(new Verified($user));
}

return app(VerificationResponse::class);
}
}
namespace App\Http\Controllers\Auth;

use App\Models\User;
use App\Http\Responses\VerificationResponse;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Http\Responses\Auth\Contracts\EmailVerificationResponse;

class EmailVerificationController
{
public function __invoke(Request $request): EmailVerificationResponse
{
/** @var MustVerifyEmail $user */
$user = User::find($request->route('id'));

if ((!$user->hasVerifiedEmail()) && $user->markEmailAsVerified()) {
event(new Verified($user));
}

return app(VerificationResponse::class);
}
}
As you can see, this returns my own VerificationResponse, which implements the EmailVerificationResponse interface. and then in my web.php routes file, I have this route: Route::get('email/verify/{id}/{hash}', [EmailVerificationController::class, '__invoke'])->middleware(['signed', 'guest'])->name('verification.verify'); Hopefully that helps! oh, you also need this on your panel provider: ->emailVerification(EmailVerificationController::class)
toufikkhattara
toufikkhattara11mo ago
I see. I was confused at first since you did not mention that you extended the EmailVerificationController although it was somewhat obvious. I actually figured it out this way and implemented a new function toResponse inside the controller which I called from the route too. My case and implementation was slightly different and included the whole logic inside one function. Your solution was very insightful and not oddly specific 🙂 class CustomVerificationResponse extends EmailVerificationController { public function toResponse(Request $request): RedirectResponse { $user = User::find($request->route('id')); if ($user->role === 'admin') { return app(EmailVerificationResponse::class)->toResponse($request); } $user->markEmailAsVerified(); $token = Password::broker()->createToken($user); $url = Filament::getResetPasswordUrl($token, $user); return redirect()->to($url); } }
Jon Mason
Jon MasonOP11mo ago
awesome, glad you got it figured out!
ROOT-LEE
ROOT-LEE10mo ago
Hi, I'm testing the same features. where can i call $this->sendEmailVerificationNotification() ?
I have implemented it. App\Http\Controllers\Auth\EmailVerificationController App\Http\Controllers\Responses\VerificationResponse and UserObserver
public
function created(User $user): void
{


// Check the role of the creator and assign the corresponding role to the new user
if (!auth()->user()->isAdmin()) {
if (auth()->user()->isEtablissement()) {
$user->assignRole('service_commun');


$user->sendEmailVerificationNotification();


} else {
$user->assignRole('etablissement');
}
}


}
public
function created(User $user): void
{


// Check the role of the creator and assign the corresponding role to the new user
if (!auth()->user()->isAdmin()) {
if (auth()->user()->isEtablissement()) {
$user->assignRole('service_commun');


$user->sendEmailVerificationNotification();


} else {
$user->assignRole('etablissement');
}
}


}
Thanks 🙂 Ohh there was a session opened in another tab , everything works fine !!
Jon Mason
Jon MasonOP9mo ago
On your user model, make sure you have:
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail);
}
Where VerifyEmail is a notification class containing a toMail method:
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);

if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}

$url = Filament::getVerifyEmailUrl($notifiable);
return $this->buildMailMessage($verificationUrl);
}
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);

if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}

$url = Filament::getVerifyEmailUrl($notifiable);
return $this->buildMailMessage($verificationUrl);
}
I'm having some issues with my implementation, so can't promise you all of the above is correct. Trying to work through it now.
NathanaelGT
NathanaelGT7mo ago
For future readers, try this
// service provider
use Filament\Facades\Filament;
use Illuminate\Auth\Notifications\VerifyEmail;

VerifyEmail::createUrlUsing(Filament::getVerifyEmailUrl(...));
// service provider
use Filament\Facades\Filament;
use Illuminate\Auth\Notifications\VerifyEmail;

VerifyEmail::createUrlUsing(Filament::getVerifyEmailUrl(...));
Want results from more Discord servers?
Add your server