cesargb / laravel-magiclink

Create link for authenticate in Laravel without password or get private content
MIT License
383 stars 44 forks source link

Call to a member function run() on null #106

Open LarryBarker opened 11 months ago

LarryBarker commented 11 months ago

We've been seeing this error more frequently which seems to happen when a user tries to use a magic link with an invalid token. We send magic links in an onboarding email to allow users access to complete their profile, and we think users are going back to this email and using the link to try and login again.

I've tracked this back to the getMagicLinkByToken method on the MagicLink class: https://github.com/cesargb/laravel-magiclink/blob/225c3c289ec68a66c9f93761accee132744ab85c/src/MagicLink.php#L187

In our case, this is returning null for whatever reason, and then when the MagicLinkController tries to call run(), its calling it on null and throwing the exception.

Do you have any ideas on how we can work around this? Ideally, if someone visits a link where the token is invalid, we could show them a page with more information and maybe a form to resend a magic link (we would implement this ourselves if possible)?

Thank you for taking time to share this package - it has been very helpful in getting our onboarding flow figured out. Any and all feedback is appreciated, and thank you again 🙏

cesargb commented 11 months ago

A MagicLink, by default, expires after 72 hours, but you can create a MagicLink without an expiration date.

To do this, when you generate the MagicLink, you can use the second argument to specify the expiration time in minutes. Optionally, you can pass null to ensure that it doesn't expire over time.

Please visit lifetime options

Let me provide you with an example:

$lifetime = null;

MagicLink::create($action, $lifetime);

Please tell me if this solves your issue.

LarryBarker commented 9 months ago

@cesargb Thanks for your reply. The issue is not with creating magic links, or their expiration I don't believe; this is an issue where the record does not exist in the database for whatever reason. For example, if someone tried using a link like this:

myapp.com/magiclink/this-is-an-example-of-a-token-that-doesnt-exist-in-the-database

getMagicLinkByToken($token) in will return null and MagicLinkController will throw an exception.

labomatik commented 8 months ago

Same error on my side, to avoid that i've to use my own route and modify the getMagicLinkByToken function to test the validity of the token..

To reproduce: create a new link to login, pass the middleware, delete the record in the database and you got the error

georgiarnaudov commented 7 months ago

I'm getting the same error when listening for visited event and try to delete the link by using $event->magiclink->delete()

jamesdb commented 4 months ago

Same error on my side, to avoid that i've to use my own route and modify the getMagicLinkByToken function to test the validity of the token..

To reproduce: create a new link to login, pass the middleware, delete the record in the database and you got the error

Interesting, i wonder if its the HEAD requests which are triggering the errors. Explains why i've seen a few of these on production but no one has reported anything.

Might be worth trying something like this:

<?php

namespace MagicLink\Middlewares;

use Closure;
use Illuminate\Http\Request;
use MagicLink\MagicLink;
use MagicLink\Responses\Response;

class MagiclinkMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        $token = (string) $request->route('token');

        $magicLink = MagicLink::getValidMagicLinkByToken($token);

        if ($request->method() === 'HEAD') {
            return response()->noContent(($magicLink) ? 200 : 404);
        }

        if (! $magicLink) {
            return $this->badResponse();
        }

        $responseAccessCode = $magicLink->getResponseAccessCode();

        if ($responseAccessCode) {
            return $responseAccessCode;
        }

        $magicLink->visited();

        return $next($request);
    }

    protected function badResponse()
    {
        $responseClass = config('magiclink.invalid_response.class', Response::class);

        $response = new $responseClass();

        return $response(config('magiclink.invalid_response.options', []));
    }
}

Instead of continuing on and executing the MagicLinkController.