Skip to content

Commit 52fb337

Browse files
authored
Migrate actions add option --dry-run and doc (#146)
1 parent 7025c32 commit 52fb337

3 files changed

Lines changed: 75 additions & 15 deletions

File tree

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# MagicLink for Laravels App
32

43
Through the `MagicLink` class we can create a secure link that later
@@ -11,6 +10,9 @@ offer secure content and even log in to the application.
1110
[![Quality Score](https://img.shields.io/scrutinizer/g/cesargb/laravel-magiclink.svg?style=flat-square)](https://scrutinizer-ci.com/g/cesargb/laravel-magiclink)
1211
[![Total Downloads](https://img.shields.io/packagist/dt/cesargb/laravel-magiclink.svg?style=flat-square)](https://packagist.org/packages/cesargb/laravel-magiclink)
1312

13+
> [!WARNING]
14+
> Migrate actions is required if you are upgrading from version 2.24.2 or earlier. Please see the [Migrate actions](#migrate-actions) section for detailed instructions.
15+
1416
## Contents
1517

1618
- [Installation](#installation)
@@ -28,6 +30,7 @@ offer secure content and even log in to the application.
2830
- [Events](#events)
2931
- [Customization](#customization)
3032
- [Rate limiting](#rate-limiting)
33+
- [Migrate actions](#migrate-actions)
3134
- [Testing](#testing)
3235
- [Contributing](#contributing)
3336
- [Security](#security)
@@ -450,6 +453,26 @@ to limit the requests. For example, to limit the requests to 100 per minute, set
450453
MAGICLINK_RATE_LIMIT=100
451454
```
452455

456+
## Migrate actions
457+
458+
> [!WARNING]
459+
> The action storage mechanism changed from PHP serialization to HMAC-signed JSON format for improved security.
460+
> If you're upgrading from version 2.24.2, you need to migrate existing MagicLinks.
461+
462+
To migrate legacy serialized actions to the new format, run:
463+
464+
```bash
465+
php artisan magiclink:migrate --dry-run
466+
```
467+
468+
This command will simulate the migration and display the number of legacy MagicLinks found.
469+
470+
If everything looks good, run the migration for real with:
471+
472+
```bash
473+
php artisan magiclink:migrate
474+
```
475+
453476
## Testing
454477

455478
Run the tests with:

src/Commands/MigrateLegacyActionsCommand.php

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,24 @@
1212
class MigrateLegacyActionsCommand extends Command
1313
{
1414
protected $signature = 'magiclink:migrate
15-
{--force : Force the operation to run without confirmation}';
15+
{--force : Force the operation to run without confirmation}
16+
{--dry-run : Show what would be migrated without actually migrating}';
1617

1718
protected $description = 'Migrate legacy serialized actions to the new HMAC-signed format';
1819

1920
private int $migrated = 0;
2021
private array $errors = [];
2122
private ProgressBar $progressBar;
23+
private bool $dryRun = false;
2224

2325
public function handle()
2426
{
27+
$this->dryRun = $this->option('dry-run');
28+
29+
if ($this->dryRun) {
30+
$this->warn('Running in DRY-RUN mode - no changes will be made');
31+
}
32+
2533
$this->info('Checking for legacy MagicLinks...');
2634

2735
$legacyCount = $this->getLegacyBuilder()->count();
@@ -67,7 +75,7 @@ public function handle()
6775

6876
private function canContinue(int $legacyCount): bool
6977
{
70-
if ($this->option('force')) {
78+
if ($this->option('force') || $this->dryRun) {
7179
return true;
7280
}
7381

@@ -78,8 +86,13 @@ private function printSummary(): void
7886
{
7987
$this->newLine(2);
8088

81-
$this->info("Migration completed!");
82-
$this->info("Successfully migrated: {$this->migrated}");
89+
if ($this->dryRun) {
90+
$this->info("Dry-run completed!");
91+
$this->info("Would migrate: {$this->migrated}");
92+
} else {
93+
$this->info("Migration completed!");
94+
$this->info("Successfully migrated: {$this->migrated}");
95+
}
8396

8497
if ($failed = count($this->errors) > 0) {
8598
$this->error("Failed to migrate: {$failed}");
@@ -118,11 +131,13 @@ private function migrateRecord(int|string $id, string $action): array
118131
];
119132
}
120133

121-
$magicLink->action = (new MagicLink())->getConnection()->getDriverName() === 'pgsql'
122-
? unserialize(base64_decode($action))
123-
: unserialize($action);
134+
if (! $this->dryRun) {
135+
$magicLink->action = (new MagicLink())->getConnection()->getDriverName() === 'pgsql'
136+
? unserialize(base64_decode($action))
137+
: unserialize($action);
124138

125-
$magicLink->saveQuietly();
139+
$magicLink->saveQuietly();
140+
}
126141

127142
return [
128143
'status' => 'success',

tests/Commands/MigrateLegacyActionsCommandTest.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public function test_controller()
1919
{
2020
$action = new ControllerAction(MyController::class);
2121

22-
[$id, $magiclinkUrl] = $this->generateMagicLink($action);
22+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink($action);
2323

2424
$this->get($magiclinkUrl)
2525
->assertStatus(200)
@@ -39,7 +39,7 @@ public function test_controller()
3939

4040
public function test_download_file()
4141
{
42-
[$id, $magiclinkUrl] = $this->generateMagicLink(new DownloadFileAction('text.txt'));
42+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink(new DownloadFileAction('text.txt'));
4343

4444
$this->get($magiclinkUrl)
4545
->assertStatus(200)
@@ -65,7 +65,7 @@ public function test_download_file()
6565

6666
public function test_auth()
6767
{
68-
[$id, $magiclinkUrl] = $this->generateMagicLink(new LoginAction(User::first()));
68+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink(new LoginAction(User::first()));
6969

7070
$this->artisan('magiclink:migrate', ['--force' => true])
7171
->assertSuccessful();
@@ -83,7 +83,7 @@ public function test_auth()
8383

8484
public function test_response_callable()
8585
{
86-
[$id, $magiclinkUrl] = $this->generateMagicLink(new ResponseAction(
86+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink(new ResponseAction(
8787
function () {
8888
return 'callback called';
8989
}
@@ -107,7 +107,7 @@ function () {
107107

108108
public function test_view()
109109
{
110-
[$id, $magiclinkUrl] = $this->generateMagicLink(new ViewAction('view'));
110+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink(new ViewAction('view'));
111111

112112
$this->get($magiclinkUrl)
113113
->assertStatus(200)
@@ -125,7 +125,29 @@ public function test_view()
125125
->assertSeeText('This is a tests view');
126126
}
127127

128-
private function generateMagicLink($action): array
128+
public function test_dry_run()
129+
{
130+
$action = new ControllerAction(MyController::class);
131+
132+
[$id, $magiclinkUrl] = $this->generateLegacyMagicLink($action);
133+
134+
$this->get($magiclinkUrl)
135+
->assertStatus(200)
136+
->assertSeeText('im a controller invoke');
137+
138+
$actionLegacy = DB::table('magic_links')->where('id', $id)->first()->action;
139+
140+
$this->artisan('magiclink:migrate', ['--dry-run' => true])
141+
->assertSuccessful();
142+
143+
$this->assertEquals($actionLegacy, DB::table('magic_links')->where('id', $id)->first()->action);
144+
145+
$this->get($magiclinkUrl)
146+
->assertStatus(200)
147+
->assertSeeText('im a controller invoke');
148+
}
149+
150+
private function generateLegacyMagicLink($action): array
129151
{
130152
$id = (string) \Illuminate\Support\Str::uuid();
131153
$token = 'toktok';

0 commit comments

Comments
 (0)