Skip to content

Commit 8305146

Browse files
4.x
1 parent 0bc981c commit 8305146

11 files changed

Lines changed: 424 additions & 85 deletions

.github/workflows/php.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,13 @@ jobs:
5454
strategy:
5555
matrix:
5656
php-version:
57-
- 8.2
5857
- 8.3
5958
- 8.4
59+
- 8.5
6060
laravel-constraint:
61-
- 11.*
6261
- 12.*
62+
- 13.*
6363
dependencies: [ lowest, highest ]
64-
exclude:
65-
- laravel-constraint: 12.*
66-
php-version: 8.2
6764

6865
steps:
6966
- name: Set up PHP

DATABASE.md

Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,44 @@
11
# Model customization
22

3-
You can further customize the package Models using the `customize()` method with a callback that receives the freshly instanced model instance. For example, you may want to hide some attributes, or change the table and connection the Model by default. Preferably, you would do this in your `bootstrap/app.php`.
3+
Most of the time you will want to change the table name of the Model, maybe because it collides with another. Use the `customize()` method with the table name. Preferably, you would do this in your `bootstrap/app.php`.
44

55
```php
66
use Illuminate\Foundation\Application;
7-
use Illuminate\Foundation\Configuration\Exceptions;
8-
use Illuminate\Foundation\Configuration\Middleware;
9-
use Vendor\Package\Models\Driver;
7+
use Vendor\Package\Models\Car;
108

119
return Application::configure(basePath: dirname(__DIR__))
1210
->booted(function () {
13-
// Customize the model
14-
Car::customize(function (Car $model) {
15-
$model->setTable('my_custom_car');
16-
$model->setConnection('readonly-mysql');
17-
18-
$model->setHidden('private_notes');
19-
})
11+
// Customize the model table name.
12+
Car::customize('vehicles');
2013
})->create();
2114
```
2215

23-
> [!TIP]
24-
>
25-
> For your convenience, the Migration will automatically pick up the table and connection you set in the Model.
26-
27-
## Changing the table name
16+
You can further customize the Models included in this package with a callback that receives a Model instance. This method is always executed when instancing a Model.
2817

29-
To change the table the model should use, use the customize method inside the `->booted(...)` callback in `bootstrap/app.php` or within the `boot` method of a `ServiceProvider`.
18+
For example, you may hide some attributes or change the table and connection the Model by default.
3019

3120
```php
32-
TwoFactorAuthentication::customize(function (TwoFactorAuthentication $model) {
33-
$model->setTable('my_custom_table');
21+
use Vendor\Package\Models\Car;
22+
23+
Car::customize(function (Car $model) {
24+
$model->setTable('vehicles');
25+
$model->setConnection('readonly-mysql');
26+
27+
$model->setHidden('private_notes');
3428
});
3529
```
3630

37-
> [!NOTE]
31+
> [!TIP]
32+
>
33+
> There is no need to alter the Model migration if you change the table. The migration automatically picks up the table and connection you set in through the `customize()` method.
3834
>
39-
> If you're migrating from 2.x to 3.x, the property `TwoFactorAuthentication::$useTable = 'my_custom_table';` is deprecated. Instead, set the custom table name using the `customize` method as shown above.
40-
4135
4236
# Migration customization
4337

4438
The library you have installed comes with a very hands-off approach for migrations. If you check the new migrations published at `database/migrations`, you will find something very similar to this:
4539

4640
```php
47-
// database/migrations/2022_01_01_193000_create_cars_migration.php
41+
// database/migrations/2027_01_01_193000_create_cars_table.php
4842
use Vendor\Package\Models\Car;
4943

5044
return Car::migration();
@@ -54,7 +48,7 @@ Worry not, the migration will still work. It has been _simplified_ for easy cust
5448

5549
## Adding columns
5650

57-
To add columns to the migration, add a callback to the `with()` method. The callback will receive the table blueprint so you can modify the table while it's being created.
51+
To add columns to the migration, add a callback to the `with()` method. The callback will receive the table blueprint, so you can modify the table before it is created. New columns will be appended to the table blueprint.
5852

5953
```php
6054
use Illuminate\Database\Schema\Blueprint;
@@ -66,50 +60,45 @@ return Car::migration()->with(function (Blueprint $table) {
6660
});
6761
```
6862

69-
> [!NOTE]
70-
>
71-
> The columns you add will be created _after_ the package adds its own columns. In other words, these will be set at the end of the table.
72-
7363
### Relationships
7464

75-
If the package supports it, you may add relationships through their proper migration columns. For example, if we want to add the `driver` relationship to the model, we can use the native `resolveRelationUsing()` on your `bootstrap/app.php()`.
65+
If the package supports it, you may add relationships through their proper migration columns. For example, if we want to add the `car` relationship to the package Model, we can use the native `resolveRelationUsing()` on your `bootstrap/app.php()`.
7666

7767
```php
68+
use App\Models\Driver;
7869
use Illuminate\Foundation\Application;
79-
use Illuminate\Foundation\Configuration\Exceptions;
80-
use Illuminate\Foundation\Configuration\Middleware;
81-
use Vendor\Package\Models\Driver;
70+
use Vendor\Package\Models\Car;
8271

8372
return Application::configure(basePath: dirname(__DIR__))
8473
->booted(function () {
8574
// Add the relationship.
86-
Car::resolveRelationUsing('driver', function (Car $car) {
87-
return $car->belongsTo(Driver::class, 'driver_id')
75+
Car::resolveRelationUsing('driver', function (Driver $driver) {
76+
return $driver->belongsTo(Driver::class, 'driver_id');
8877
})
8978
})->create();
9079
```
9180

92-
In the published package migration, you should be able to add the required column to connect your model like normal. In this case, we can use the [`foreignIdFor()`](https://laravel.com/docs/migrations#column-method-foreignIdFor) method to safely set the proper column type.
81+
In the published package migration, you should be able to add the required column to connect your model like usual. In this case, we can use the [`foreignIdFor()`](https://laravel.com/docs/migrations#column-method-foreignIdFor) method to safely set the proper column name and type.
9382

9483
```php
9584
use App\Models\Driver;
9685
use Illuminate\Database\Schema\Blueprint;
97-
use Laragear\Package\Models\Car;
86+
use Vendor\Package\Models\Car;
9887

99-
return Car::migration(function (Blueprint $table) {
88+
return Car::migration()->with(function (Blueprint $table) {
10089
// ...
10190

102-
$table->foreignIdFor(Driver::class);
91+
$table->foreignIdFor(Driver::class, 'driver_id');
10392
});
10493
```
10594

10695
## After Up & Before Down
10796

108-
If you need to execute logic after creating the table, or before dropping it, use the `afterUp()` and `beforeDown()` methods, respectively.
97+
If you need to execute logic _after_ creating the table, or _before_ dropping it, use the `afterUp()` and `beforeDown()` methods, respectively.
10998

11099
```php
111100
use Illuminate\Database\Schema\Blueprint;
112-
use Laragear\Package\Models\Car;
101+
use Vendor\Package\Models\Car;
113102

114103
return Car::migration()
115104
->afterUp(function (Blueprint $table) {
@@ -120,27 +109,25 @@ return Car::migration()
120109
});
121110
```
122111

123-
## Morphs
112+
### Morphs
124113

125-
This package _may_ create a morph relation automatically, with the intent to easily handle default relationship across multiple models. For example, a morph migration to support an `owner` being either one of your models `Company` or `Person`.
114+
Some packages will create a morph relation automatically to easily handle the default relationship across multiple models. For example, a morph migration to support an `owner` being either one of your models `user` or `business`.
126115

127116
```php
128-
use Laragear\Package\Models\Car;
117+
use Vendor\Package\Models\Car;
129118

130119
$car = Car::find(1);
131120

132-
$owners = $car->owner; // App/Models/Company or App/Models/Person
121+
$owner = $driver->owner; // App/Models/User or App/Models/Business
133122
```
134123

135-
You may find yourself with youur models using a primary key type, different to the type used by the library models. For example, your `Company` or `Person` models using UUID, while the migration morph type uses integers.
124+
You may find yourself with models that use UUID, ULID or other types of primary keys, but with a migration that creates morphs for integer primary keys.
136125

137-
If that's your case, you can change the library morph type with the `morph...` property access (preferably), or the `morph()` method with `numeric`, `uuid` or `ulid` if you need to also set an index name (in case your database engine doesn't play nice with large ones).
138-
139-
For example, you can change the morph type of the `Car` migration to match the UUID type for the `Company` and `Person` models.
126+
You can change the morph type with the `morph...` property access preferably, or the `morph()` method with `numeric`, `uuid` or `ulid` if you need to also set an index name (in case your database engine doesn't play nice with large ones).
140127

141128
```php
142129
use Illuminate\Database\Schema\Blueprint;
143-
use Laragear\Package\Models\Car;
130+
use Vendor\Package\Models\Car;
144131

145132
return Car::migration()->morphUuid;
146133

README.md

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,16 @@ Your support allows me to keep this package free, up-to-date and maintainable. A
3838

3939
## Requirements
4040

41-
* Laravel 11 or later
41+
* PHP 8.3 or later
42+
* Laravel 12 or later
4243

4344
## Installation
4445

4546
Fire up Composer and require this package in your project.
4647

47-
composer require laragear/two-factor
48+
```shell
49+
composer require laragear/two-factor
50+
```
4851

4952
That's it.
5053

@@ -105,9 +108,9 @@ To enable Two-Factor Authentication for the User, he must sync the Shared Secret
105108

106109
> [!TIP]
107110
>
108-
> Free Authenticator Apps, in no particular order, are [iOS Authenticator](https://www.apple.com/ios/), [FreeOTP](https://freeotp.github.io/), [Authy](https://authy.com/), [2FAS](https://2fas.com/), [2Stable Authenticator](https://authenticator.2stable.com/), [Step-two](https://steptwo.app/), [BinaryRoot Authenticator](https://www.binaryboot.com/totp-authenticator), [Google](https://apps.apple.com/app/google-authenticator/id388497605) [Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en), and [Microsoft Authenticator](https://www.microsoft.com/en-us/account/authenticator), to name a few.
111+
> Free Authenticator Apps, in no particular order, are [iOS Authenticator](https://www.apple.com/ios/), [FreeOTP](https://freeotp.github.io/), [Authy](https://authy.com/), [2FAS](https://2fas.com/), [2Stable Authenticator](https://authenticator.2stable.com/), [Step-two](https://steptwo.app/), [BinaryRoot Authenticator](https://www.binaryboot.com/totp-authenticator) and [Google](https://apps.apple.com/app/google-authenticator/id388497605) [Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en).
109112
110-
To start, generate the needed data using the `createTwoFactorAuth()` method. This returns a serializable _Shared Secret_ that you can show to the User as a string or QR Code (encoded as SVG) in your view.
113+
To start, generate the necessary data using the `createTwoFactorAuth()` method. This returns a serializable _Shared Secret_ that you can show to the User as a string or QR Code (encoded as SVG) in your view.
111114

112115
```php
113116
use Illuminate\Http\Request;
@@ -406,6 +409,109 @@ Route::get('api/important-token', function () {
406409
})->middleware('2fa.require', '2fa.confirm:my-redirect-route-name,true');
407410
```
408411

412+
### Throttling Middleware
413+
414+
This library includes a global throttling middleware. It will trigger a TOTP confirmation once a number of Requests done to the application exceed a default threshold.
415+
416+
To enable the global middleware, set the `OTP_THROTTLE` environment variable to `true`.
417+
418+
```dotenv
419+
OTP_THROTTLE
420+
```
421+
422+
Alternatively, you can enable it globally and also set the threshold using `{tries},{decay}` notation. For example, to change the threshold to a maximum of 3 requests each 15 seconds.
423+
424+
```dotenv
425+
OTP_THROTTLE=3,15
426+
```
427+
428+
You may configure the global throttling additional features in the [configuration file](#throttling).
429+
430+
> [!IMPORTANT]
431+
>
432+
> The Throttling Middleware uses your application [Rate Limiter](https://laravel.com/docs/12.x/rate-limiting).
433+
434+
#### Using a custom throttle key
435+
436+
By default, the key to throttle the request is generated using the IP.
437+
438+
You may want to create your own key based on the request by setting a callback in the `$key` static property of the `Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor` class, in your `bootstrap/app.php` file. For example, you may add a string to differentiate multiple users from the same IP.
439+
440+
```php
441+
use Illuminate\Foundation\Application;
442+
use Illuminate\Http\Request;
443+
use Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor;
444+
445+
return Application::configure()
446+
->booted(function () {
447+
ThrottleWithTwoFactor::$key = function (Request $request) {
448+
return $request->ip() . '|' . $request->user()->getKey();
449+
}
450+
})
451+
->create();
452+
```
453+
454+
#### Determining if the request should be throttled
455+
456+
You may programmatically throttle requests by setting a callback in the `$shouldThrottle` static property of the `Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor` class, in your `bootstrap/app.php` file.
457+
458+
```php
459+
use Illuminate\Foundation\Application;
460+
use Illuminate\Http\Request;
461+
use Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor;
462+
463+
return Application::configure()
464+
->booted(function () {
465+
ThrottleWithTwoFactor::$shouldThrottle = function (Request $request) {
466+
return $request->user()->isNotVip();
467+
}
468+
})
469+
->create();
470+
```
471+
472+
Inside the function, you can use the method `clearAttempts()` of the middleware instance present as a second parameter in the callback to clear the attempts of the given request.
473+
474+
```php
475+
use Illuminate\Foundation\Application;
476+
use Illuminate\Http\Request;
477+
use Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor;
478+
479+
return Application::configure()
480+
->booted(function () {
481+
ThrottleWithTwoFactor::$shouldThrottle = function (Request $request, ThrottleWithTwoFactor $instance) {
482+
if ($request->user()->impersontedByAdmin()) {
483+
$instance->clearAttempts($request);
484+
}
485+
486+
return $request->user()->isNotVip();
487+
}
488+
})
489+
->create();
490+
```
491+
492+
493+
#### Custom Rate Limiter Cache
494+
495+
You may want to use a custom Cache Store to use with the middleware. This requires instructing the Container to give a custom Rate Limiter created by you when resolving the `Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor` class.
496+
497+
This can be done in the `bootstrap/app.php` with the `registered()` method and [Contextual Binding](https://laravel.com/docs/12.x/container#contextual-binding).
498+
499+
```php
500+
use Illuminate\Cache\RateLimiter;
501+
use Illuminate\Foundation\Application;
502+
use Laragear\TwoFactor\Http\Middleware\ThrottleWithTwoFactor;
503+
504+
return Application::configure()
505+
->registered(function (Application $app) {
506+
$app->when(ThrottleWithTwoFactor::class)
507+
->needs(RateLimiter::class)
508+
->give(function () use ($app) {
509+
return new RateLimiter(cache()->store('memcached'))
510+
});
511+
})
512+
->create();
513+
```
514+
409515
## Validation
410516

411517
Sometimes you may want to manually trigger a TOTP validation in any part of your application for the authenticated user. You can validate a TOTP code for the authenticated user using the `topt` rule.
@@ -421,7 +527,7 @@ public function checkTotp(Request $request)
421527
}
422528
```
423529

424-
This rule will succeed only if the user is authenticated, it has Two-Factor Authentication enabled, and the code is correct or is a recovery code.
530+
This rule will succeed only if the user is authenticated, it has Two-Factor Authentication enabled, and the code is correct or is a recovery code.
425531

426532
> [!TIP]
427533
>
@@ -495,6 +601,11 @@ return [
495601
'size' => 400,
496602
'margin' => 4
497603
],
604+
'throttle' => [
605+
'enable' => env('OTP_THROTTLE'),
606+
'amount' => [6, 60],
607+
'route' => '2fa.confirm'
608+
]
498609
];
499610
```
500611

@@ -639,6 +750,24 @@ return [
639750

640751
This controls the size and margin used to create the QR Code, which are created as SVG.
641752

753+
### Throttling
754+
755+
```php
756+
return [
757+
'throttle' => [
758+
'enable' => env('OTP_THROTTLE', false),
759+
'tries' => [6, 60],
760+
'session_key' => '_totp_throttle',
761+
'route' => '2fa.throttle'
762+
'lifetime' => 10
763+
],
764+
];
765+
```
766+
767+
This configures the [throttling global middleware](#throttling-middleware). The most important part is the route to redirect the user once the application throttles the user request, and where in the session to save this data.
768+
769+
The `lifetime` key sets how many minutes to "bypass" the throttler after the user has been already throttled.
770+
642771
## Custom TOTP Label
643772

644773
You may change how your model creates a TOTP Label, which is shown to the user on its authenticator, using the `getTwoFactorIssuer()` and `getTwoFactorUserIdentifier()` methods of your user.

0 commit comments

Comments
 (0)