Skip to content

Commit

Permalink
Add rate limit (#128)
Browse files Browse the repository at this point in the history
* add rate limit

* add none to disable rate limit

* add env MAGICLINK_RATE_LIMIT

* add docs
  • Loading branch information
cesargb authored Jan 4, 2025
1 parent 330e0a1 commit 566eb98
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 37 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ offer secure content and even log in to the application.
- [Lifetime](#lifetime)
- [Events](#events)
- [Customization](#customization)
- [Rate limiting](#rate-limiting)
- [Testing](#testing)
- [Contributing](#contributing)
- [Security](#security)

## Installation

Expand Down Expand Up @@ -326,8 +330,8 @@ php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag

And edit the file `config/magiclink.php`


### Migrations

To customize the migration files of this package you need to publish the migration files:

```bash
Expand All @@ -336,7 +340,6 @@ php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag

You'll find the published files in `database/migrations/*`


### Custom response when magiclink is invalid

When the magicLink is invalid by default the http request return a status 403.
Expand Down Expand Up @@ -407,6 +410,19 @@ return a `view()`
],
```

## Rate limiting

You can limit the number of requests per minute for a magic link. To do this, you need to
set the `MAGICLINK_RATE_LIMIT` environment variable to the desired value.

By default, the rate limit is 100 attempts per minutes. Use `none` to disable the rate limit.

```bash
# .env

MAGICLINK_RATE_LIMIT='none'
```

## Testing

Run the tests with:
Expand Down
28 changes: 27 additions & 1 deletion config/magiclink.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
<?php


return [

'access_code' => [
/*
|--------------------------------------------------------------------------
| Access Code View
|--------------------------------------------------------------------------
|
| Here you may specify the view to ask for access code.
|
*/
'view' => 'magiclink::ask-for-access-code-form',
],

Expand Down Expand Up @@ -32,7 +41,24 @@
'class' => MagicLink\Responses\Response::class,
],

'token' => [
'middlewares' => [
'throttle:magiclink',
MagicLink\Middlewares\MagiclinkMiddleware::class,
'web',
],

/*
|--------------------------------------------------------------------------
| Rate Limit
|--------------------------------------------------------------------------
|
| Here you may specify the number of attempts to rate limit per minutes
|
| Default: 100, if you want to disable rate limit, set as 'none'
*/
'rate_limit' => env('MAGICLINK_RATE_LIMIT', 100),

'token' => [
/*
|--------------------------------------------------------------------------
| Token size
Expand Down
6 changes: 1 addition & 5 deletions routes/routes.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
<?php

use Illuminate\Support\Facades\Route;
use MagicLink\Middlewares\MagiclinkMiddleware;

Route::group(
[
'middleware' => [
MagiclinkMiddleware::class,
'web',
],
'middleware' => config('magiclink.middlewares'),
],
function () {
Route::get(
Expand Down
16 changes: 16 additions & 0 deletions src/MagicLinkServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace MagicLink;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class MagicLinkServiceProvider extends ServiceProvider
Expand All @@ -15,11 +17,25 @@ public function boot()
{
$this->offerPublishing();

$this->registerRateLimit();

$this->loadRouteMagicLink();

$this->loadViewMagicLink();
}

private function registerRateLimit(): void
{
$rateLimit = config('magiclink.rate_limit', 100);

RateLimiter::for(
'magiclink',
fn () => $rateLimit === 'none'
? Limit::none()
: Limit::perMinute($rateLimit)
);
}

private function loadRouteMagicLink(): void
{
$disableRegisterRoute = config('magiclink.disable_default_route', false);
Expand Down
38 changes: 38 additions & 0 deletions tests/Http/HttpHeadTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace MagicLink\Test\Http;

use MagicLink\Actions\ResponseAction;
use MagicLink\MagicLink;
use MagicLink\Test\TestCase;

class HttpHeadTest extends TestCase
{
public function test_http_head_request_has_not_effects()
{
$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$magiclink->num_visits = 4;
$magiclink->save();

$this->head($magiclink->url)
->assertStatus(200)
->assertDontSeeText('private content');

$magiclink->refresh();

$this->assertEquals(4, $magiclink->num_visits);
}

public function test_http_head_request_without_valid_magiclink()
{
$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$this->head($magiclink->url . '-bad')
->assertStatus(404);
}
}
31 changes: 2 additions & 29 deletions tests/HttpTest.php → tests/Http/HttpTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

namespace MagicLink\Test;
namespace MagicLink\Test\Http;

use MagicLink\Actions\ResponseAction;
use MagicLink\MagicLink;
use MagicLink\Test\TestCase;

class HttpTest extends TestCase
{
Expand All @@ -25,24 +26,6 @@ public function test_http_get_request()
$this->assertEquals(5, $magiclink->num_visits);
}

public function test_http_head_request_has_not_effects()
{
$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$magiclink->num_visits = 4;
$magiclink->save();

$this->head($magiclink->url)
->assertStatus(200)
->assertDontSeeText('private content');

$magiclink->refresh();

$this->assertEquals(4, $magiclink->num_visits);
}

public function test_http_options_request_has_not_effects()
{
$magiclink = MagicLink::create(new ResponseAction(function () {
Expand Down Expand Up @@ -73,14 +56,4 @@ public function test_http_urlencode_legacy()
->assertStatus(200)
->assertSeeText('private content');
}

public function test_http_head_request_without_valid_magiclink()
{
$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$this->head($magiclink->url . '-bad')
->assertStatus(404);
}
}
44 changes: 44 additions & 0 deletions tests/Http/HttpThrottleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace MagicLink\Test\Http;

use MagicLink\Actions\ResponseAction;
use MagicLink\MagicLink;
use MagicLink\MagicLinkServiceProvider;
use MagicLink\Test\TestCase;

class HttpThrottleTest extends TestCase
{
public function test_http_failed_when_rate_limit_is_exceeded()
{
config(['magiclink.rate_limit' => 1]);
(new MagicLinkServiceProvider($this->app))->boot();

$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$this->get($magiclink->url)
->assertStatus(200);

$this->get($magiclink->url)

->assertStatus(429);
}
public function test_http_when_rate_limit_is_none()
{
config(['magiclink.rate_limit' => 'none']);
(new MagicLinkServiceProvider($this->app))->boot();

$magiclink = MagicLink::create(new ResponseAction(function () {
return 'private content';
}));

$this->get($magiclink->url)
->assertStatus(200);

$this->get($magiclink->url)
->assertStatus(200);
}

}

0 comments on commit 566eb98

Please sign in to comment.