Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use concerns of laravel/ui #25

Open
Jubeki opened this issue May 12, 2020 · 6 comments
Open

Use concerns of laravel/ui #25

Jubeki opened this issue May 12, 2020 · 6 comments
Labels
enhancement New feature or request

Comments

@Jubeki
Copy link

Jubeki commented May 12, 2020

Many solutions of laravel are not accessible if you are using this preset (including security features).
For example the concern AuthenticatesUsers.
It ships with throttling login attempts and choosing a custom username() instead of only email.

Maybe the concerns should be added but then this preset or better laravel/ui cannot be uninstalled because the concerns are implemented there.

@imliam
Copy link
Contributor

imliam commented May 12, 2020

Choosing a custom username is as simple as editing the email fields there, just as you'd override the username() method in the default LoginController - I don't think that concern is missing functionality necessarily.

However, I will admit that we forgot to implement the login throttling behaviour that comes from the ThrottleLogins trait - we'll have to see about adding that back and see what the nicest way will be.

@danharrin danharrin added the enhancement New feature or request label May 18, 2020
@glaesser
Copy link

Choosing a custom username is as simple as editing the email fields there, just as you'd override the username() method in the default LoginController - I don't think that concern is missing functionality necessarily.

However, I will admit that we forgot to implement the login throttling behaviour that comes from the ThrottleLogins trait - we'll have to see about adding that back and see what the nicest way will be.

Did this ever materialize? Searching for any sort of login-throttling behaviour in vain. It's not like we can just use throttling middleware from the routes file as livewire POSTs to it's own endpoints and not the component/page route. And the "ThrottleLogins" trait requires use of $request, which we don't have access to in livewire components. Any suggestions?

@Jubeki
Copy link
Author

Jubeki commented Nov 15, 2020

You can update your livewire config:

  1. Publish the config using php artisan livewire:publish
  2. Edit config/livewire.php
return [
  // ...

  'middleware_group' => ['web', 'throttle:5,1'],

  // ...
];

This will then be applied to all Livewire Components

Edit: Otherwise you can take a look at traits where you implement your throttle logic:
https://laravel-livewire.com/docs/2.x/traits

@glaesser
Copy link

@Jubeki thanks for the pointers! Will the throttling apply to the livewire endpoint (/livewire/*) or just the frontend route (on which you could already use regular laravel route throttling)?

Ideally you would only want very conservative/slow throttling applied to the login/register methods.

@Jubeki
Copy link
Author

Jubeki commented Nov 15, 2020

The throttling should be applied to the /livewire endpoint. Otherwise the following authorization example would be meaningless: https://laravel-livewire.com/docs/2.x/authorization

You could make a feature request on https://github.com/livewire/livewire for middleware for components.
There is also the livewire discord for questions and ideas: https://discord.gg/livewire

I will tag Caleb here. Maybe he has some ideas.
cc @calebporzio

@mikesaintsg
Copy link

@Jubeki great solution as a fail-safe on all routes but blows up a route with an ugly 403 error. Instead, I found a way to use a validation rule for more specific throttling and a graceful way to relay that to the end user.

@glaesser thanks to @stevebauman I was able to come up with a custom throttle validation rule that follows the laravel ui methodology and by extension a custom Lockout Event since both required access to the Request object.

Give this a shot when you get the chance:

App\Events\Lockout.php

<?php

namespace App\Events;

class Lockout
{
    public $key, $identifier;

    public function __construct(string $key, string $identifier = '')
    {
        $this->key = $key;
        $this->identifier = $identifier;
    }
}

App\Rules\Throttle.php

<?php

namespace App\Rules;

use Illuminate\Cache\RateLimiter;
use Illuminate\Contracts\Validation\Rule;

class Throttle implements Rule
{
    protected $throttleKey, $maxAttempts, $decayInMinutes, $event;

    public function __construct($throttleKey, $maxAttempts, $decayInMinutes, $event = null)
    {
        $this->throttleKey = $throttleKey;
        $this->maxAttempts = $maxAttempts;
        $this->decayInMinutes = $decayInMinutes;
        $this->event = $event;
    }

    public function passes($attribute, $value): bool
    {
        if ($this->hasTooManyAttempts()) {

            if($this->event) {

                event(new $this->event($this->throttleKey, $value));
            }

            return false;
        }

        $this->incrementAttempts();

        return true;
    }

    public function message()
    {
        $seconds = $this->limiter()
            ->availableIn($this->throttleKey);

        return trans('auth.throttle', [
            'seconds' => $seconds,
            'minutes' => ceil($seconds / 60),
        ]);
    }

    protected function hasTooManyAttempts()
    {
        return $this->limiter()->tooManyAttempts(
            $this->throttleKey, $this->maxAttempts
        );
    }

    protected function limiter()
    {
        return app(RateLimiter::class);
    }

    protected function incrementAttempts()
    {
        $this->limiter()->hit(
            $this->throttleKey, $this->decayInMinutes * 60
        );
    }
}

App\Http\Livewire\Auth\Login.php

<?php

namespace App\Http\Livewire\Auth;

use App\Events\Lockout;
use App\Rules\Throttle;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Livewire\Component;

class Login extends Component
{
    public $ip, $email, $password, $remember = false;

    protected function rules(): array
    {
        return [
            'email' => [
                'required',
                'email',
                new Throttle($this->throttleKey(), 5, 1, Lockout::class)],
            'password' => ['required'],
        ];
    }

    public function mount(Request $request)
    {
        $this->ip = $request->ip();
    }

    public function login()
    {
        $input = $this->validate();

        if (Auth::attempt($input, $this->remember)) {
            session()->regenerate();

            return redirect()->intended(route('home'));
        }

        return $this->addError('password', trans('auth.failed'));
    }

    protected function throttleKey()
    {
        return Str::lower("$this->email|$this->ip");
    }

    public function render()
    {
        return view('livewire.auth.login');
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants