From d37307785f8f63d12db2b6ab47f7c006c9e49a5e Mon Sep 17 00:00:00 2001 From: Kovah Date: Wed, 20 Jul 2022 10:39:39 +0200 Subject: [PATCH] Properly migrate existing API tokens, adjust register user command --- app/Console/Commands/RegisterUserCommand.php | 62 ++++++++++++------- app/Models/User.php | 3 - .../2022_06_23_112431_migrate_user_data.php | 28 +++++++-- tests/Commands/RegisterUserCommandTest.php | 18 +++--- tests/Migrations/UserDataMigrationTest.php | 34 ++++++++++ 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/app/Console/Commands/RegisterUserCommand.php b/app/Console/Commands/RegisterUserCommand.php index 665cac1e..7c4301fd 100644 --- a/app/Console/Commands/RegisterUserCommand.php +++ b/app/Console/Commands/RegisterUserCommand.php @@ -3,43 +3,57 @@ namespace App\Console\Commands; use App\Actions\Fortify\CreateNewUser; -use App\Actions\Settings\SetDefaultSettingsForUser; -use App\Models\User; use Illuminate\Console\Command; +use Illuminate\Validation\ValidationException; class RegisterUserCommand extends Command { protected $signature = 'registeruser {name? : Username} {email? : User email address}'; protected $description = 'Register a new user with a given user name and an email address.'; + private ?string $userName; + private ?string $userEmail; + private ?string $userPassword; + private bool $validationFailed = false; + public function handle(): void { - $name = $this->argument('name'); - $email = $this->argument('email'); + $this->userName = $this->argument('name'); + $this->userEmail = $this->argument('email'); - if (empty($name)) { - $name = $this->ask('Please enter the user name'); + do { + $this->askForUserDetails(); + + try { + (new CreateNewUser)->create([ + 'name' => $this->userName, + 'email' => $this->userEmail, + 'password' => $this->userPassword, + 'password_confirmation' => $this->userPassword, + ]); + + $this->validationFailed = false; + } catch (ValidationException $e) { + $this->validationFailed = true; + foreach ($e->errors() as $error) { + $this->error(implode(' ', $error)); + } + } + } while ($this->validationFailed); + + $this->info('User ' . $this->userName . ' registered.'); + } + + protected function askForUserDetails(): void + { + if (empty($this->userName) || $this->validationFailed) { + $this->userName = $this->ask('Please enter the user name containing only alpha-numeric characters, dashes or underscores', $this->userName); } - if (empty($email)) { - $email = $this->ask('Please enter the user email address'); + if (empty($this->userEmail) || $this->validationFailed) { + $this->userEmail = $this->ask('Please enter the user email address', $this->userEmail); } - // Check if the user exists - if (User::where('email', $email)->first()) { - $this->error('An user with the email address "' . $email . '" already exists!'); - return; - } - - $password = $this->secret('Please enter a password for ' . $name); - - (new CreateNewUser)->create([ - 'name' => $name, - 'email' => $email, - 'password' => $password, - 'password_confirmation' => $password, - ]); - - $this->info('User ' . $name . ' registered.'); + $this->userPassword = $this->secret('Please enter a password for ' . $this->userName); } } diff --git a/app/Models/User.php b/app/Models/User.php index d2b7d745..a3d7197e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -22,7 +22,6 @@ use Spatie\Permission\Traits\HasRoles; * @property string $email * @property string $password * @property string|null $remember_token - * @property string|null $api_token * @property string|null $two_factor_recovery_codes * @property string|null $two_factor_secret * @property Carbon|null $created_at @@ -42,14 +41,12 @@ class User extends Authenticatable implements Auditable 'name', 'email', 'password', - 'api_token', 'blocked_at', ]; protected $hidden = [ 'password', 'remember_token', - 'api_token', ]; protected $casts = [ diff --git a/database/migrations/2022_06_23_112431_migrate_user_data.php b/database/migrations/2022_06_23_112431_migrate_user_data.php index 743265ab..2139f81d 100644 --- a/database/migrations/2022_06_23_112431_migrate_user_data.php +++ b/database/migrations/2022_06_23_112431_migrate_user_data.php @@ -1,5 +1,6 @@ migrateNoteVisibility(); $this->addUserRoles(); + $this->migrateApiTokens(); } protected function migrateLinkVisibility(): void @@ -54,7 +57,7 @@ class MigrateUserData extends Migration $table->integer('visibility')->default(ModelAttribute::VISIBILITY_PRIVATE)->after('is_private'); }); - LinkList::withTrashed()->get()->each(function ($list) { + LinkList::withTrashed()->get()->each(function (LinkList $list) { $list->visibility = match ((bool)$list->is_private) { true => ModelAttribute::VISIBILITY_PRIVATE, false => $this->guestAccessEnabled @@ -74,7 +77,7 @@ class MigrateUserData extends Migration $table->integer('visibility')->default(ModelAttribute::VISIBILITY_PRIVATE)->after('is_private'); }); - Tag::withTrashed()->get()->each(function ($tag) { + Tag::withTrashed()->get()->each(function (Tag $tag) { $tag->visibility = match ((bool)$tag->is_private) { true => ModelAttribute::VISIBILITY_PRIVATE, false => $this->guestAccessEnabled @@ -94,7 +97,7 @@ class MigrateUserData extends Migration $table->integer('visibility')->default(ModelAttribute::VISIBILITY_PRIVATE)->after('is_private'); }); - Note::withTrashed()->get()->each(function ($note) { + Note::withTrashed()->get()->each(function (Note $note) { $note->visibility = match ((bool)$note->is_private) { true => ModelAttribute::VISIBILITY_PRIVATE, false => $this->guestAccessEnabled @@ -110,7 +113,7 @@ class MigrateUserData extends Migration protected function addUserRoles(): void { - \Illuminate\Support\Facades\Artisan::call('db:seed', ['--class' => 'RolesAndPermissionsSeeder']); + Artisan::call('db:seed', ['--class' => 'RolesAndPermissionsSeeder']); Schema::table('users', function (Blueprint $table) { $table->timestamp('blocked_at')->nullable()->after('api_token'); @@ -122,4 +125,19 @@ class MigrateUserData extends Migration $newAdmin->assignRole(Role::ADMIN); } } + + public function migrateApiTokens(): void + { + User::all()->each(function (User $user) { + $user->tokens()->create([ + 'name' => 'MigratedApiToken', + 'token' => hash('sha256', $user->api_token), + 'abilities' => [ApiToken::ABILITY_USER_ACCESS], + ]); + }); + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('api_token'); + }); + } } diff --git a/tests/Commands/RegisterUserCommandTest.php b/tests/Commands/RegisterUserCommandTest.php index 41a11ae4..947acbfd 100644 --- a/tests/Commands/RegisterUserCommandTest.php +++ b/tests/Commands/RegisterUserCommandTest.php @@ -3,7 +3,6 @@ namespace Tests\Commands; use App\Models\User; -use App\Settings\SettingsAudit; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -25,8 +24,6 @@ class RegisterUserCommandTest extends TestCase $databaseUser = User::latest('id')->first(); - //var_dump(User::all()); - $this->assertEquals('Test', $databaseUser->name); $this->assertEquals('test@linkace.org', $databaseUser->email); } @@ -36,7 +33,7 @@ class RegisterUserCommandTest extends TestCase User::factory()->create(); // Create admin dummy user $this->artisan('registeruser') - ->expectsQuestion('Please enter the user name', 'Test') + ->expectsQuestion('Please enter the user name containing only alpha-numeric characters, dashes or underscores', 'Test') ->expectsQuestion('Please enter the user email address', 'test@linkace.org') ->expectsQuestion('Please enter a password for Test', 'testpassword') ->expectsOutput('User Test registered.') @@ -50,13 +47,20 @@ class RegisterUserCommandTest extends TestCase public function testCommandWithDuplicateUser(): void { - User::factory()->create(['email' => 'test@linkace.org']); + User::factory()->create(['name' => 'Test', 'email' => 'test@linkace.org']); $this->artisan('registeruser', [ 'name' => 'Test', 'email' => 'test@linkace.org', ]) - ->expectsOutput('An user with the email address "test@linkace.org" already exists!') - ->assertExitCode(0); + ->expectsQuestion('Please enter a password for Test', 'testpassword') + ->expectsOutput('The name has already been taken.') + ->expectsOutput('The email has already been taken.') + ->expectsQuestion('Please enter the user name containing only alpha-numeric characters, dashes or underscores', 'Test2') + ->expectsQuestion('Please enter the user email address', 'test2@linkace.org') + ->expectsQuestion('Please enter a password for Test2', 'testpassword') + ->expectsOutput('User Test2 registered.') + ->assertExitCode(0) + ; } } diff --git a/tests/Migrations/UserDataMigrationTest.php b/tests/Migrations/UserDataMigrationTest.php index aa410051..8f11dd46 100644 --- a/tests/Migrations/UserDataMigrationTest.php +++ b/tests/Migrations/UserDataMigrationTest.php @@ -6,7 +6,11 @@ use App\Models\Link; use App\Models\LinkList; use App\Models\Note; use App\Models\Tag; +use App\Models\User; use App\Settings\SystemSettings; +use Illuminate\Support\Facades\DB; +use Laravel\Sanctum\Sanctum; +use Laravel\Sanctum\SanctumServiceProvider; use Tests\TestCase; class UserDataMigrationTest extends TestCase @@ -260,4 +264,34 @@ class UserDataMigrationTest extends TestCase 'visibility' => 1, // is public ]); } + + public function testUserApiTokenMigration(): void + { + $this->migrateUpTo('2022_06_23_112431_migrate_user_data.php'); + + DB::table('users')->insert([ + 'name' => 'test', + 'email' => 'test@linkace.org', + 'password' => 'test', + 'api_token' => 'testApiToken', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + $this->artisan('migrate'); + + $this->assertDatabaseHas('personal_access_tokens', [ + 'tokenable_type' => 'App\Models\User', + 'tokenable_id' => '1', + 'name' => 'MigratedApiToken', + 'abilities' => '["user_access"]', + ]); + + // Test if the token is valid + Link::factory()->create(['url' => 'https://token-test.com']); + + $this->get('links/feed', [ + 'Authorization' => 'Bearer testApiToken' + ])->assertOk()->assertSee('https://token-test.com'); + } }