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');
+ }
+
+}