Nuno
Nuno

Reputation: 311

Tenancy for laravel - Verification Link sent from central domain

i'm using Tenancy For Laravel and followed the installation and quickstart from their documentation. Everything is working fine except the verification link:

The idea is register on my central app and everything else happening on my tenants. Register is working fine (creates a tenant, a domain and a user on the tenant database), login is working fine on the tenant side, so is password verification and password recovery/reset. The only thing not working fine is the email verification (Everytime a user register the system send the email with the link pointing to the central domain instead of the tenant one.) But if the user taps "resend" it sends it from the tenant domain this time. This is my code:

RegisteredTenantController

class RegisteredTenantController extends Controller 
{
    public function create()
    {
        return view('auth.register');
    }

    public function store(RegisterTenantRequest $request)
    {
        $tenant = Tenant::create($request->validated());
        $tenant->createDomain(['domain' => $request->domain]);

        return redirect(tenant_route($tenant->domains->first()->domain, 'tenant.login'));
    }
}

TenancyServiceProvider

class TenancyServiceProvider extends ServiceProvider
{
    (...)
    
    public function events()
    {
        return [
            (...)
            Events\TenantCreated::class => [
                JobPipeline::make([
                    Jobs\CreateDatabase::class,
                    Jobs\MigrateDatabase::class,
                    CreateTenantAdmin::class,
                ])->send(function (Events\TenantCreated $event) {
                    return $event->tenant;
                })->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
            ],
            (...)
        ];
    }

    (...)
}

CreateTenantAdmin

class CreateTenantAdmin implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;


    public function __construct(public Tenant $tenant)
    {
        //
    }

    public function handle()
    {
        $this->tenant->run(function($tenant) {
            $user = User::create([
                'name' => $tenant->name,
                'email' => $tenant->email,
                'password' => $tenant->password,
            ]);

            $user->sendEmailVerificationNotification();
        });
    }
}

User

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable;

    (...)

    public function sendEmailVerificationNotification()
    {
        $this->notify(new CustomVerifyEmail);
    }
}

CustomVerifyEmail

class CustomVerifyEmail extends VerifyEmail
{
    protected function verificationUrl($notifiable)
    {
        if (static::$createUrlCallback) {
            return call_user_func(static::$createUrlCallback, $notifiable);
        }

        return URL::temporarySignedRoute('tenant.verification.verify',
            Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
            [
                'id' => $notifiable->getKey(),
                'hash' => sha1($notifiable->getEmailForVerification()),
            ]
        );
    }
}

All my routes are configured to have "tenant." prefixed to their names if they are to be served on the tenant app. (like tenant.verification.verify which works but i have to type it in)

My best guess is URL::temporarySignedRoute is grabbing whichever domain is accessing the page at that particular moment and since register happens on my central app that would explain why the "first" email has the central domain but every "retry" comes with the tenant domain.

Anyone has got this to work with this package?

Upvotes: 0

Views: 1477

Answers (2)

samavia khalid
samavia khalid

Reputation: 1

The answer might help others how I resolve this issue in Laravel 11. This way it keeps the logic simple and separte from AppServiceProvider Simply customized the URL in app/Providers/TenancyServiceProvider.php:boot()

public function boot()
{
    $this->bootEvents();
    $this->mapRoutes();
   
 //following lines do the job 
    ResetPassword::createUrlUsing(function (object $notifiable, string $token) {
        $tenant = tenant('id');
        return config('app.frontend_url')."/$tenant/password-reset/$token&email={$notifiable->getEmailForPasswordReset()}";
    });

    $this->makeTenancyMiddlewareHighestPriority();
}

Upvotes: 0

Nuno
Nuno

Reputation: 311

This is what i ended up doing:

Add this to Illuminate\Routing\UrlGenerator

public function temporaryTenantSignedRoute($name, $expiration, $parameters = [], $absolute = true)
{
    return $this->tenantSignedRoute($name, $parameters, $expiration, $absolute);
}

public function tenantSignedRoute($name, $parameters = [], $expiration = null, $absolute = true)
{
    $this->ensureSignedRouteParametersAreNotReserved(
        $parameters = Arr::wrap($parameters)
    );

    if ($expiration) {
        $parameters = $parameters + ['expires' => $this->availableAt($expiration)];
    }

    ksort($parameters);

    $key = call_user_func($this->keyResolver);

    return tenant_route(tenant()->domains->first()->domain, $name, $parameters + [
        'signature' => hash_hmac('sha256', tenant_route(tenant()->domains->first()->domain, $name, $parameters, $absolute), $key),
    ], $absolute);
}

Add this to Illuminate\Support\Facades\URL

@method static string temporaryTenantSignedRoute(string $name, \DateTimeInterface|\DateInterval|int $expiration, array $parameters = [], bool $absolute = true)

In CustomVerifyEmail change this

Url::temporarySignedRoute

to this

Url::temporaryTenantSignedRoute

Upvotes: 0

Related Questions