diff --git a/app/Policies/PortfolioPolicy.php b/app/Policies/PortfolioPolicy.php index e4e44c8..a3228e9 100644 --- a/app/Policies/PortfolioPolicy.php +++ b/app/Policies/PortfolioPolicy.php @@ -15,7 +15,7 @@ class PortfolioPolicy { $pivot = $portfolio->users()->where('user_id', $user->id)->first(); - return $pivot; + return !!$pivot; } /** diff --git a/lang/en.json b/lang/en.json index 3386f04..882fc31 100644 --- a/lang/en.json +++ b/lang/en.json @@ -358,7 +358,7 @@ "By removing this person's access, they will no longer be able to view this portfolio. They will lose access immediately.": "By removing this person's access, they will no longer be able to view this portfolio. They will lose access immediately.", "Hey again!": "Hey again!", - "Before you can get started with Investbrain, you'll want to create a password:": "Before you can get started with Investbrain, you'll want to create a password:", + "Before you can get started with Investbrain, let's complete your profile:": "Before you can get started with Investbrain, let's complete your profile:", "Or login with SSO:": "Or login with SSO:", "Create Password": "Create Password" diff --git a/lang/es.json b/lang/es.json index 9411b7f..c89d3b2 100644 --- a/lang/es.json +++ b/lang/es.json @@ -358,7 +358,7 @@ "By removing this person's access, they will no longer be able to view this portfolio. They will lose access immediately.": "Al eliminar el acceso de esta persona, ya no podrá ver este portafolio. Perderán el acceso inmediatamente.", "Hey again!": "¡Oye de nuevo!", - "Before you can get started with Investbrain, you'll want to create a password:": "Antes de poder comenzar a utilizar Investbrain, deberá crear una cuenta:", + "Before you can get started with Investbrain, let's complete your profile:": "Antes de poder comenzar a utilizar Investbrain, deberá crear una cuenta:", "Or login with SSO:": "O iniciar sesión mediante SSO:", "Create Password": "Crear Contraseña" } \ No newline at end of file diff --git a/resources/views/auth/invited-onboarding.blade.php b/resources/views/auth/invited-onboarding.blade.php index 7a842b4..7d03596 100644 --- a/resources/views/auth/invited-onboarding.blade.php +++ b/resources/views/auth/invited-onboarding.blade.php @@ -7,7 +7,7 @@

{{ __('Hey again!') }} 👋

-

{{ __('Before you can get started with Investbrain, you\'ll want to create a password:') }}

+

{{ __('Before you can get started with Investbrain, let\'s complete your profile:') }}

@livewire('invited-onboarding-form', [ 'portfolio' => $portfolio, diff --git a/resources/views/livewire/share-portfolio-form.blade.php b/resources/views/livewire/share-portfolio-form.blade.php index ddc4e73..29f4751 100644 --- a/resources/views/livewire/share-portfolio-form.blade.php +++ b/resources/views/livewire/share-portfolio-form.blade.php @@ -126,6 +126,7 @@ new class extends Component {
+ @if ($portfolio->owner) + @endif @foreach (collect($this->portfolio?->users)->where('pivot.owner', '!=', 1) as $user) name('policy.s Route::get('auth/verify/{connected_account}', [ConnectedAccountController::class, 'verify'])->name('oauth.verify_connected_account'); Route::get('auth/{provider}', [ConnectedAccountController::class, 'redirectToProvider'])->name('oauth.redirect'); -Route::get('auth/{provider}/callback', [ConnectedAccountController::class, 'handleProviderCallback']); +Route::get('auth/{provider}/callback', [ConnectedAccountController::class, 'handleProviderCallback'])->name('oauth.callback'); diff --git a/tests/ConnectedAccountTest.php b/tests/ConnectedAccountTest.php new file mode 100644 index 0000000..f97f600 --- /dev/null +++ b/tests/ConnectedAccountTest.php @@ -0,0 +1,168 @@ +controller = new ConnectedAccountController(); + } + + public function test_handle_provider_callback_with_already_connected_account() + { + $provider = 'github'; + config(['services.enabled_login_providers' => 'github,google']); + + // Create a user and a connected account for the provider + $user = User::create([ + 'name' => 'Alice Smith', + 'email' => 'alice@example.com', + 'email_verified_at' => now(), + ]); + $providerUser = (object)[ + 'id' => '67890', + 'name' => 'Alice Smith', + 'email' => 'alice@example.com', + 'token' => '15932t8', + 'tokenSecret' => null, + 'refreshToken' => null, + 'expiresIn' => null, + ]; + ConnectedAccount::forceCreate([ + 'provider' => $provider, + 'provider_id' => $providerUser->id, + 'user_id' => $user->id, + 'token' => $providerUser->token, + 'verified_at' => now() + ]); + + Socialite::shouldReceive('driver') + ->with($provider) + ->andReturnSelf() + ->shouldReceive('user') + ->andReturn($providerUser); + + $response = $this->get(route('oauth.callback', ['provider' => $provider])); + + $this->assertTrue(Auth::check()); + $this->assertEquals($user->id, Auth::id()); + + $response->assertRedirect(route('dashboard')); + } + + public function test_handle_provider_callback_with_new_user() + { + $provider = 'github'; + config(['services.enabled_login_providers' => 'github,google']); + $providerUser = (object)[ + 'id' => '12345', + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'token' => 'token', + 'tokenSecret' => null, + 'refreshToken' => null, + 'expiresIn' => null, + ]; + + Socialite::shouldReceive('driver') + ->with($provider) + ->andReturnSelf() + ->shouldReceive('user') + ->andReturn($providerUser); + + $response = $this->get(route('oauth.callback', ['provider' => $provider])); + + $user = User::where('email', 'john@example.com')->first(); + $this->assertNotNull($user); + $this->assertEquals('John Doe', $user->name); + + $connectedAccount = ConnectedAccount::first(); + $this->assertNotNull($connectedAccount); + $this->assertEquals('github', $connectedAccount->provider); + $this->assertEquals('12345', $connectedAccount->provider_id); + $this->assertNotNull($connectedAccount->verified_at); + + $this->assertTrue(Auth::check()); + $response->assertRedirect(route('dashboard')); + } + + public function test_handle_provider_callback_with_existing_account() + { + $provider = 'github'; + config(['services.enabled_login_providers' => 'github,google']); + User::create([ + 'name' => 'Jane Doe', + 'email' => 'jane@example.com', + 'email_verified_at' => now(), + ]); + $providerUser = (object)[ + 'id' => '54321', + 'name' => 'Jane Doe', + 'email' => 'jane@example.com', + 'token' => 'token', + 'tokenSecret' => null, + 'refreshToken' => null, + 'expiresIn' => null, + ]; + + Socialite::shouldReceive('driver') + ->with($provider) + ->andReturnSelf() + ->shouldReceive('user') + ->andReturn($providerUser); + + $response = $this->get(route('oauth.callback', ['provider' => $provider])); + + $connectedAccount = ConnectedAccount::first(); + $this->assertNotNull($connectedAccount); + $this->assertEquals('github', $connectedAccount->provider); + $this->assertEquals('54321', $connectedAccount->provider_id); + $this->assertNull($connectedAccount->verified_at); + + $response->assertRedirect(route('login')); + $response->assertSessionHas('status', 'Account already exists. Check your email to connect your GitHub account.'); + } + + public function test_verify_connected_account() + { + $user = User::create([ + 'name' => 'Alice Smith', + 'email' => 'alice@example.com', + 'email_verified_at' => null, + ]); + $connectedAccount = ConnectedAccount::forceCreate([ + 'provider' => 'github', + 'provider_id' => '12345', + 'token' => '0283523', + 'user_id' => $user->id, + 'verified_at' => null, + ]); + + $this->assertNull($connectedAccount->verified_at); + + $response = $this->get(route('oauth.verify_connected_account', ['connected_account' => $connectedAccount->id])); + + $connectedAccount->refresh(); + + $this->assertNotNull($connectedAccount->verified_at); + $this->assertNotNull($connectedAccount->user); + + $response->assertRedirect(route('dashboard')); + $response->assertSessionHas('toast'); + } + +} diff --git a/tests/PortfolioPolicyTest.php b/tests/PortfolioPolicyTest.php new file mode 100644 index 0000000..670354f --- /dev/null +++ b/tests/PortfolioPolicyTest.php @@ -0,0 +1,112 @@ +policy = new PortfolioPolicy(); + + $this->user = User::factory()->create(); + + Auth::login($this->user); + $this->portfolio = Portfolio::factory()->create(); + + // Attach the users to the portfolio + $this->portfolio->users()->syncWithoutDetaching([ + $this->user->id => [ + 'full_access' => false, + 'owner' => false, + ] + ]); + } + + public function test_stranger_access_viaweb() + { + $user = User::factory()->create(); + + $result = $this->actingAs($user)->get(route('portfolio.show', ['portfolio' => $this->portfolio])); + + $result->assertStatus(403); + } + + public function test_stranger_access_via_policy() + { + $user = User::factory()->create(); + + $result = $this->policy->readOnly($user, $this->portfolio); + $this->assertFalse($result, 'User should not have readonly access'); + + $result = $this->policy->fullAccess($user, $this->portfolio); + $this->assertFalse($result, 'User should not have full access'); + + $result = $this->policy->owner($user, $this->portfolio); + $this->assertFalse($result, 'User should not have owner access'); + } + + public function test_read_only_policy() + { + $result = $this->policy->readOnly($this->user, $this->portfolio); + $this->assertTrue($result, 'User should have read-only access'); + } + + public function test_read_only_via_web() + { + $result = $this->actingAs($this->user)->get(route('portfolio.show', ['portfolio' => $this->portfolio])); + + $result->assertStatus(200); + } + + public function test_full_access_policy_with_full_access() + { + // Update pivot table to give full access + $this->portfolio->users()->updateExistingPivot($this->user->id, [ + 'full_access' => true, + ]); + + $result = $this->policy->fullAccess($this->user, $this->portfolio); + $this->assertTrue($result, 'User should have full access'); + } + + public function test_full_access_policy_without_full_access() + { + // Check that the user doesn't have full access + $result = $this->policy->fullAccess($this->user, $this->portfolio); + $this->assertFalse($result, 'User should not have full access'); + } + + public function test_owner_policy_when_user_is_owner() + { + // Update pivot table to make the user the owner + $this->portfolio->users()->updateExistingPivot($this->user->id, [ + 'owner' => true, + ]); + + $result = $this->policy->owner($this->user, $this->portfolio); + $this->assertTrue($result, 'User should be the owner'); + } + + public function test_owner_policy_when_user_is_not_owner() + { + // Check that the user is not the owner + $result = $this->policy->owner($this->user, $this->portfolio); + $this->assertFalse($result, 'User should not be the owner'); + } + +}