├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── magiclink.php ├── databases └── migrations │ ├── 2017_07_06_000000_create_table_magic_links.php │ ├── 2021_03_06_211907_add_access_code_to_magic_links_table.php │ └── 2024_12_25_000000_add_indexes_to_magic_links_table.php ├── resources └── views │ └── ask-for-access-code-form.blade.php ├── routes └── routes.php └── src ├── AccessCode.php ├── Actions ├── ActionAbstract.php ├── ControllerAction.php ├── DownloadFileAction.php ├── LoginAction.php ├── ResponseAction.php └── ViewAction.php ├── Controllers └── MagicLinkController.php ├── Events ├── MagicLinkWasCreated.php ├── MagicLinkWasDeleted.php └── MagicLinkWasVisited.php ├── MagicLink.php ├── MagicLinkServiceProvider.php ├── Middlewares └── MagiclinkMiddleware.php └── Responses ├── AbortResponse.php ├── RedirectResponse.php ├── Response.php ├── ResponseContract.php └── ViewResponse.php /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on 6 | [Github](https://github.com/cesargb/laravel-magiclink). 7 | 8 | 9 | ## Pull Requests 10 | 11 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 12 | 13 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 14 | 15 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 16 | 17 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 18 | 19 | - **Create feature branches** - Don't ask us to pull from your master branch. 20 | 21 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 22 | 23 | 24 | ## Running Tests 25 | 26 | ``` bash 27 | $ composer test 28 | ``` -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cesargb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # MagicLink for Laravels App 3 | 4 | Through the `MagicLink` class we can create a secure link that later 5 | being visited will perform certain actions, which will allow us 6 | offer secure content and even log in to the application. 7 | 8 | [](https://packagist.org/packages/cesargb/laravel-magiclink) 9 |  10 | [](https://github.com/cesargb/laravel-magiclink/actions/workflows/style-fix.yml) 11 | [](https://scrutinizer-ci.com/g/cesargb/laravel-magiclink) 12 | [](https://packagist.org/packages/cesargb/laravel-magiclink) 13 | 14 | ## Contents 15 | 16 | - [Installation](#installation) 17 | - [Use case](#use-case) 18 | - [Create a MagicLink](#create-a-magiclink) 19 | - [Actions](#actions) 20 | - [Login](#login-action) 21 | - [Download file](#download-file-action) 22 | - [View](#view-action) 23 | - [Http Response](#http-response-action) 24 | - [Controller](#controller-action) 25 | - [Custom Action](#custom-action) 26 | - [Protect with an access code](#protect-with-an-access-code) 27 | - [Lifetime](#lifetime) 28 | - [Events](#events) 29 | - [Customization](#customization) 30 | - [Rate limiting](#rate-limiting) 31 | - [Testing](#testing) 32 | - [Contributing](#contributing) 33 | - [Security](#security) 34 | 35 | ## Installation 36 | 37 | You can install this package via composer using: 38 | 39 | ```bash 40 | composer require cesargb/laravel-magiclink 41 | ``` 42 | 43 | ### Preparing the database 44 | 45 | You need to publish the migration to create the `magic_links` table: 46 | 47 | ```bash 48 | php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="migrations" 49 | ``` 50 | 51 | After that, you need to run migrations. 52 | 53 | ```bash 54 | php artisan migrate 55 | ``` 56 | 57 | ## Use case 58 | 59 | With this example you can create a link to auto login on your application with 60 | the desired user: 61 | 62 | ```php 63 | use MagicLink\Actions\LoginAction; 64 | use MagicLink\MagicLink; 65 | 66 | $urlToAutoLogin = MagicLink::create(new LoginAction($user))->url 67 | ``` 68 | 69 | ## Create a MagicLink 70 | 71 | The `MagicLink` class has the `create` method to generate a class that through 72 | the `url` property we will obtain the link that we will send to our visitor. 73 | 74 | This method requires the action to be performed. 75 | 76 | ## Actions 77 | 78 | Each MagicLink is associated with an action, which is what will be performed 79 | once the link is visited. 80 | 81 | - [Login Action](#login-action) 82 | - [Download file Action](#download-file-action) 83 | - [View Action](#view-action) 84 | - [Http Response Action](#http-response-action) 85 | - [Http Response](#http-response-action) 86 | - [Controller](#controller-action) 87 | - [Custom Action](#custom-action) 88 | - [Custom Base URL](#custom-base-url) 89 | 90 | ### Login Action 91 | 92 | Through the `LoginAction` action, you can log in to the application using the 93 | generated link by `MagicLink`. 94 | 95 | Your constructor supports the user who will login. Optionally we can specify 96 | the [HTTP response](https://laravel.com/docs/master/responses) using the method 97 | `response` or specify other guard with method `guard`. 98 | 99 | Examples: 100 | 101 | ```php 102 | use MagicLink\Actions\LoginAction; 103 | use MagicLink\MagicLink; 104 | 105 | // Sample 1; Login and redirect to dash board 106 | $action = new LoginAction(User::first()); 107 | $action->response(redirect('/dashboard')); 108 | 109 | $urlToDashBoard = MagicLink::create($action)->url; 110 | 111 | // Sample 2; Login and view forms to password reset and use guard web 112 | $action = new LoginAction(User::first()); 113 | $action->response(view('password.reset', ['email' => 'user@example.tld'])); 114 | 115 | $urlShowView = MagicLink::create($action)->url; 116 | 117 | // Sample 3; Login in other guard and redirect default 118 | $action = new LoginAction(User::first()); 119 | $action->guard('customguard'); 120 | 121 | $urlShowView = MagicLink::create($action)->url; 122 | 123 | // Sample 4; Login and remember me 124 | $action = new LoginAction(User::first()); 125 | $action->remember(); 126 | 127 | $urlShowView = MagicLink::create($action)->url; 128 | ``` 129 | 130 | ### Download file Action 131 | 132 | This action, `DownloadFileAction`, permit create a link to download a private file. 133 | 134 | The constructor require the file path. 135 | 136 | Example: 137 | 138 | ```php 139 | use MagicLink\Actions\DownloadFileAction; 140 | use MagicLink\MagicLink; 141 | 142 | // Url to download the file storage_app('private_document.pdf') 143 | $url = MagicLink::create(new DownloadFileAction('private_document.pdf'))->url; 144 | 145 | // Download file with other file_name 146 | $action = new DownloadFileAction('private_document.pdf', 'your_document.pdf'); 147 | $url = MagicLink::create($action)->url; 148 | 149 | // Download file from other disk 150 | $action = new DownloadFileAction('private_document.pdf')->disk('ftp'); 151 | $url = MagicLink::create($action)->url; 152 | 153 | ``` 154 | 155 | ### View Action 156 | 157 | With the action `ViewAction`, you can provide access to the view. You can use 158 | in his constructor the same arguments than method `view` of Laravel. 159 | 160 | Example: 161 | 162 | ```php 163 | use MagicLink\Actions\ViewAction; 164 | use MagicLink\MagicLink; 165 | 166 | // Url to view a internal.blade.php 167 | $url = MagicLink::create(new ViewAction('internal', [ 168 | 'data' => 'Your private custom content', 169 | ]))->url; 170 | ``` 171 | 172 | ### Http Response Action 173 | 174 | Through the `ResponseAction` action we can access private content without need 175 | login. Its constructor accepts as argument the 176 | [HTTP response](https://laravel.com/docs/responses) 177 | which will be the response of the request. 178 | 179 | Examples: 180 | 181 | ```php 182 | use MagicLink\Actions\ResponseAction; 183 | use MagicLink\MagicLink; 184 | 185 | $action = new ResponseAction(function () { 186 | Auth::login(User::first()); 187 | 188 | return redirect('/change_password'); 189 | }); 190 | 191 | $urlToCustomFunction = MagicLink::create($action)->url; 192 | ``` 193 | 194 | ### Controller Action 195 | 196 | `MagicLink` can directly call a controller via the `ControllerAction` action. 197 | 198 | The constructor requires one argument, the name of the controller class. With 199 | the second argument can call any controller method, by default it will use the 200 | `__invoke` method. 201 | 202 | ```php 203 | use MagicLink\Actions\ControllerAction; 204 | use MagicLink\MagicLink; 205 | 206 | // Call the method __invoke of the controller 207 | $url = MagicLink::create(new ControllerAction(MyController::class))->url; 208 | 209 | // Call the method show of the controller 210 | $url = MagicLink::create(new ControllerAction(MyController::class, 'show'))->url; 211 | ``` 212 | 213 | ### Custom Action 214 | 215 | You can create your own action class, for them you just need to extend with 216 | `MagicLink\Actions\ActionAbstract` 217 | 218 | ```php 219 | use MagicLink\Actions\ActionAbstract; 220 | 221 | class MyCustomAction extends ActionAbstract 222 | { 223 | public function __construct(public int $variable) 224 | { 225 | } 226 | 227 | public function run() 228 | { 229 | // Do something 230 | 231 | return response()->json([ 232 | 'success' => true, 233 | 'data' => $this->variable, 234 | ]); 235 | } 236 | } 237 | ``` 238 | 239 | You can now generate a Magiclink with the custom action 240 | 241 | ```php 242 | use MagicLink\MagicLink; 243 | 244 | $action = new MyCustomAction('Hello world'); 245 | 246 | $urlToCustomAction = MagicLink::create($action)->url; 247 | ``` 248 | 249 | ### Custom Base URL 250 | 251 | To set the base URL for a magic link, you can use the `baseUrl` method. This method ensures that the provided base URL has a trailing slash, making it ready for URL generation. 252 | 253 | ```php 254 | $magiclink = MagicLink::create($action); 255 | 256 | $magiclink->baseUrl("http://example.com"); 257 | 258 | $urlShowView = $magiclink->url; // http://example.com/magiclink/... 259 | ``` 260 | 261 | ## Protect with an access code 262 | 263 | Optionally you can protect the resources with an access code. 264 | You can set the access code with method `protectWithAccessCode` 265 | which accepts an argument with the access code. 266 | 267 | ```php 268 | $magiclink = MagicLink::create(new DownloadFileAction('private_document.pdf')); 269 | 270 | $magiclink->protectWithAccessCode('secret'); 271 | 272 | $urlToSend = $magiclink->url; 273 | ``` 274 | 275 | ### Custom view for access code 276 | 277 | You can customize the view of the access code form with the config file `magiclink.php`: 278 | 279 | ```php 280 | 'access_code' => [ 281 | 'view' => 'magiclink::access-code', // Change with your view 282 | ], 283 | ``` 284 | 285 | This is the [default view](/resources/views/ask-for-access-code-form.blade.php) 286 | 287 | ## Lifetime 288 | 289 | By default a link will be available for 72 hours after your creation. We can 290 | modify the life time in minutes of the link by the `$lifetime` option 291 | available in the `create` method. This argument accepts the value `null` so 292 | that it does not expire in time. 293 | 294 | ```php 295 | $lifetime = 60; // 60 minutes 296 | 297 | $magiclink = MagicLink::create(new ResponseAction(), $lifetime); 298 | 299 | $urlToSend = $magiclink->url; 300 | ``` 301 | 302 | We also have another option `$numMaxVisits`, with which we can define the 303 | number of times the link can be visited, `null` by default indicates that there 304 | are no visit limits. 305 | 306 | ```php 307 | $lifetime = null; // not expired in the time 308 | $numMaxVisits = 1; // Only can visit one time 309 | 310 | $magiclink = MagicLink::create(new ResponseAction(), $lifetime, $numMaxVisits); 311 | 312 | $urlToSend = $magiclink->url; 313 | ``` 314 | 315 | ## Events 316 | 317 | MagicLink can fires three events: 318 | 319 | ### MagicLinkWasCreated 320 | 321 | Event `MagicLink\Events\MagicLinkWasCreated` 322 | 323 | This event is fired when a magic link is created. 324 | 325 | ### MagicLinkWasVisited 326 | 327 | Event `MagicLink\Events\MagicLinkWasVisited` 328 | 329 | This event is fired when a magic link is visited. 330 | 331 | ### MagicLinkWasDeleted 332 | 333 | Event `MagicLink\Events\MagicLinkWasDeleted` 334 | 335 | This event is fired when you disable mass deletion. Add this line in your 336 | `.env` file to disable mass deletion: 337 | 338 | ```.env 339 | # Disable mass deletion for enable event MagicLinkWasDeleted 340 | MAGICLINK_DELETE_MASSIVE=false 341 | ``` 342 | 343 | > [!WARNING] 344 | > If you disable mass deletion, the cleanup will be performed one by one. 345 | > If you have many records, this can be an issue. 346 | 347 | ## Customization 348 | 349 | ### Config 350 | 351 | To custom this package you can publish the config file: 352 | 353 | ```bash 354 | php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="config" 355 | ``` 356 | 357 | And edit the file `config/magiclink.php` 358 | 359 | ### Migrations 360 | 361 | To customize the migration files of this package you need to publish the migration files: 362 | 363 | ```bash 364 | php artisan vendor:publish --provider="MagicLink\MagicLinkServiceProvider" --tag="migrations" 365 | ``` 366 | 367 | You'll find the published files in `database/migrations/*` 368 | 369 | ### Custom response when magiclink is invalid 370 | 371 | When the magicLink is invalid by default the http request return a status 403. 372 | You can custom this response with config `magiclink.invalid_response`. 373 | 374 | #### Response 375 | 376 | To return a response, use class `MagicLink\Responses\Response::class` 377 | same `response()`, you can send the arguments with options 378 | 379 | Example: 380 | 381 | ```php 382 | 'invalid_response' => [ 383 | 'class' => MagicLink\Responses\Response::class, 384 | 'options' => [ 385 | 'content' => 'forbidden', 386 | 'status' => 403, 387 | ], 388 | ], 389 | ``` 390 | 391 | #### Abort 392 | 393 | To return a exception and let the framework handle the response, 394 | use class `MagicLink\Responses\AbortResponse::class`. 395 | Same `abort()`, you can send the arguments with options. 396 | 397 | Example: 398 | 399 | ```php 400 | 'invalid_response' => [ 401 | 'class' => MagicLink\Responses\AbortResponse::class, 402 | 'options' => [ 403 | 'message' => 'You Shall Not Pass!', 404 | 'status' => 403, 405 | ], 406 | ], 407 | ``` 408 | 409 | #### Redirect 410 | 411 | Define class `MagicLink\Responses\RedirectResponse::class` to 412 | return a `redirect()` 413 | 414 | ```php 415 | 'invalid_response' => [ 416 | 'class' => MagicLink\Responses\RedirectResponse::class, 417 | 'options' => [ 418 | 'to' => '/not_valid_path', 419 | 'status' => 301, 420 | ], 421 | ], 422 | ``` 423 | 424 | #### View 425 | 426 | Define class `MagicLink\Responses\ViewResponse::class` to 427 | return a `view()` 428 | 429 | ```php 430 | 'invalid_response' => [ 431 | 'class' => MagicLink\Responses\ViewResponse::class, 432 | 'options' => [ 433 | 'view' => 'invalid', 434 | 'data' => [], 435 | ], 436 | ], 437 | ``` 438 | 439 | ## Rate limiting 440 | 441 | You can limit the number of requests per minute for a magic link. To do this, you need to 442 | set the `MAGICLINK_RATE_LIMIT` environment variable to the desired value. 443 | 444 | By default, the rate limit is disable with value 'none', but you can set a value 445 | to limit the requests. For example, to limit the requests to 100 per minute, set 446 | 447 | ```bash 448 | # .env 449 | 450 | MAGICLINK_RATE_LIMIT=100 451 | ``` 452 | 453 | ## Testing 454 | 455 | Run the tests with: 456 | 457 | ``` bash 458 | composer test 459 | ``` 460 | 461 | ## Contributing 462 | 463 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 464 | 465 | ## Security 466 | 467 | If you discover any security-related issues, please email cesargb@gmail.com 468 | instead of using the issue tracker. 469 | 470 | ## License 471 | 472 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 473 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cesargb/laravel-magiclink", 3 | "description": "Create secure link for access to private data or login in Laravel without password", 4 | "keywords": [ 5 | "laravel", 6 | "auth", 7 | "login", 8 | "link", 9 | "download", 10 | "file" 11 | ], 12 | "homepage": "https://github.com/cesargb/laravel-magiclink", 13 | "authors": [ 14 | { 15 | "name": "Cesar Garcia", 16 | "email": "cesargb@gmail.com", 17 | "homepage": "https://github.com/cesargb", 18 | "role": "Developer" 19 | } 20 | ], 21 | "license": "MIT", 22 | "require": { 23 | "php": "^8.1", 24 | "illuminate/auth": "^10.0|^11.0|^12.0", 25 | "illuminate/container": "^10.0|^11.0|^12.0", 26 | "illuminate/contracts": "^10.0|^11.0|^12.0", 27 | "illuminate/database": "^10.0|^11.0|^12.0", 28 | "laravel/serializable-closure": "^1.0|^2.0", 29 | "orchestra/testbench": "^8.0|^9.0|^10.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "MagicLink\\": "src" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "MagicLink\\Test\\": "tests" 39 | } 40 | }, 41 | "scripts": { 42 | "test": "vendor/bin/phpunit" 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "MagicLink\\MagicLinkServiceProvider" 48 | ] 49 | } 50 | }, 51 | "require-dev": { 52 | "phpunit/phpunit": "^9.0|^10.5|^11.5", 53 | "friendsofphp/php-cs-fixer": "^3.9" 54 | }, 55 | "minimum-stability": "dev", 56 | "prefer-stable": true 57 | } 58 | -------------------------------------------------------------------------------- /config/magiclink.php: -------------------------------------------------------------------------------- 1 | [ 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Access Code View 10 | |-------------------------------------------------------------------------- 11 | | 12 | | Here you may specify the view to ask for access code. 13 | | 14 | */ 15 | 'view' => 'magiclink::ask-for-access-code-form', 16 | ], 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Delete Magic Link Expired massive 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Expired MagicLinks are automatically and massively deleted from the database. 24 | | If you want to disable this option, change the value to false. 25 | | 26 | | If you disable this option, expired MagicLinks will be deleted one by one 27 | | triggering the event MagicLink\Events\MagicLinkWasDeleted. 28 | | 29 | */ 30 | 'delete_massive' => env('MAGICLINK_DELETE_MASSIVE', true), 31 | 32 | /* 33 | |-------------------------------------------------------------------------- 34 | | Disable default route 35 | |-------------------------------------------------------------------------- 36 | | 37 | | If you wish use your custom controller, you can invalidate the 38 | | default route of magic link, mark this configuration as true, 39 | | and add your custom route with the middleware: 40 | | MagicLink\Middlewares\MagiclinkMiddleware 41 | | 42 | */ 43 | 'disable_default_route' => false, 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Response when token is invalid 48 | |-------------------------------------------------------------------------- 49 | | 50 | | Here you may specify the class with method __invoke to get the response 51 | | when token is invalid 52 | | 53 | */ 54 | 'invalid_response' => [ 55 | 'class' => MagicLink\Responses\Response::class, 56 | ], 57 | 58 | 'middlewares' => [ 59 | 'throttle:magiclink', 60 | MagicLink\Middlewares\MagiclinkMiddleware::class, 61 | 'web', 62 | ], 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Rate Limit 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Here you may specify the number of attempts to rate limit per minutes 70 | | 71 | | Default: none, if you want to enable rate limit, set as integer 72 | */ 73 | 'rate_limit' => env('MAGICLINK_RATE_LIMIT', 'none'), 74 | 75 | 'token' => [ 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Token size 79 | |-------------------------------------------------------------------------- 80 | | 81 | | Here you may specify the length of token to verify the identify. 82 | | Max value is 255 characters, it will be used if bigger value is set. 83 | | 84 | */ 85 | 'length' => 64, 86 | ], 87 | 88 | 'url' => [ 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | Path default to redirect 92 | |-------------------------------------------------------------------------- 93 | | 94 | | Here you may specify the name of the path you'd like to use so that 95 | | the redirect when verify correct token. 96 | | 97 | */ 98 | 'redirect_default' => '/', 99 | 100 | /* 101 | |-------------------------------------------------------------------------- 102 | | Path to Validate Token and Auto Auth 103 | |-------------------------------------------------------------------------- 104 | | 105 | | Here you may specify the name of the path you'd like to use so that 106 | | the verify token and auth in system. 107 | | 108 | */ 109 | 'validate_path' => 'magiclink', 110 | ], 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /databases/migrations/2017_07_06_000000_create_table_magic_links.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 18 | $table->string('token', 255); 19 | $table->text('action'); 20 | $table->unsignedTinyInteger('num_visits')->default(0); 21 | $table->unsignedTinyInteger('max_visits')->nullable(); 22 | $table->timestamp('available_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists(config('magiclink.magiclink_table', 'magic_links')); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /databases/migrations/2021_03_06_211907_add_access_code_to_magic_links_table.php: -------------------------------------------------------------------------------- 1 | string('access_code')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | if (Schema::hasColumn('magic_links', 'access_code')) { 29 | Schema::table('magic_links', function (Blueprint $table) { 30 | $table->dropColumn('access_code'); 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /databases/migrations/2024_12_25_000000_add_indexes_to_magic_links_table.php: -------------------------------------------------------------------------------- 1 | index('available_at'); 18 | $table->index('max_visits'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('magic_links', function (Blueprint $table) { 30 | $table->dropIndex('magic_links_available_at_index'); 31 | $table->dropIndex('magic_links_max_visits_index'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /resources/views/ask-for-access-code-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |