From 2aa260e26bf8567412e1f4fca125e3dec3ec82d5 Mon Sep 17 00:00:00 2001 From: theHocineSaad Date: Wed, 8 Oct 2025 20:52:29 +0200 Subject: [PATCH 1/6] Add GitHub account connection and disconnection functionality --- app/Actions/ConnectGitHubAccount.php | 21 +++++++ app/Actions/DisconnectGitHubAccount.php | 17 ++++++ .../Controllers/Auth/GitHubController.php | 23 +++++++- .../Settings/GitHubAccountController.php | 32 +++++++++++ app/Models/User.php | 5 ++ resources/svg/github-line.svg | 1 + .../views/users/settings/github.blade.php | 55 +++++++++++++++++++ .../views/users/settings/settings.blade.php | 9 ++- routes/web.php | 3 + 9 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 app/Actions/ConnectGitHubAccount.php create mode 100644 app/Actions/DisconnectGitHubAccount.php create mode 100644 app/Http/Controllers/Settings/GitHubAccountController.php create mode 100644 resources/svg/github-line.svg create mode 100644 resources/views/users/settings/github.blade.php diff --git a/app/Actions/ConnectGitHubAccount.php b/app/Actions/ConnectGitHubAccount.php new file mode 100644 index 000000000..a05eb80d0 --- /dev/null +++ b/app/Actions/ConnectGitHubAccount.php @@ -0,0 +1,21 @@ +update([ + 'github_id' => $socialiteUser->getId(), + 'github_username' => $socialiteUser->getNickname(), + ]); + + dispatch(new UpdateUserIdenticonStatus($user)); + } +} diff --git a/app/Actions/DisconnectGitHubAccount.php b/app/Actions/DisconnectGitHubAccount.php new file mode 100644 index 000000000..15373a9ad --- /dev/null +++ b/app/Actions/DisconnectGitHubAccount.php @@ -0,0 +1,17 @@ +update([ + 'github_id' => null, + 'github_username' => null, + 'github_has_identicon' => false, + ]); + } +} diff --git a/app/Http/Controllers/Auth/GitHubController.php b/app/Http/Controllers/Auth/GitHubController.php index cb8fa20d0..cc0a1b033 100644 --- a/app/Http/Controllers/Auth/GitHubController.php +++ b/app/Http/Controllers/Auth/GitHubController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Auth; +use App\Actions\ConnectGitHubAccount; use App\Http\Controllers\Controller; use App\Jobs\UpdateProfile; use App\Models\User; @@ -28,7 +29,7 @@ public function redirectToProvider() /** * Obtain the user information from GitHub. */ - public function handleProviderCallback() + public function handleProviderCallback(ConnectGitHubAccount $connectGitHubAccount) { try { $socialiteUser = $this->getSocialiteUser(); @@ -42,6 +43,26 @@ public function handleProviderCallback() return $socialiteUser; } + $isConnectingAttempt = session()->pull('settings.github.connect.intended', false); + if ($isConnectingAttempt) { + $currentUser = auth()->user(); + $githubId = $socialiteUser->getId(); + + // Check if the GitHub account is already connected to another user + $existingUser = User::where('github_id', $githubId)->where('id', '!=', $currentUser->id)->first(); + if ($existingUser) { + $this->error('This GitHub account is already connected to another user.'); + + return redirect(route('settings.profile')); + } + + $connectGitHubAccount($currentUser, $socialiteUser); + + $this->success('Your GitHub account has been connected.'); + + return redirect(route('settings.profile')); + } + try { $user = User::findByGitHubId($socialiteUser->getId()); } catch (ModelNotFoundException $exception) { diff --git a/app/Http/Controllers/Settings/GitHubAccountController.php b/app/Http/Controllers/Settings/GitHubAccountController.php new file mode 100644 index 000000000..f9431e4bd --- /dev/null +++ b/app/Http/Controllers/Settings/GitHubAccountController.php @@ -0,0 +1,32 @@ +middleware(Authenticate::class); + } + + public function connect(): RedirectResponse + { + session()->put('settings.github.connect.intended', true); + + return redirect(route('login.github')); + } + + public function disconnect(DisconnectGitHubAccount $disconnectGitHubAccount): RedirectResponse + { + $disconnectGitHubAccount(auth()->user()); + + $this->success('Your GitHub account has been disconnected.'); + + return redirect(route('settings.profile')); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 1914dbf3f..87e841cfc 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -118,6 +118,11 @@ public function githubUsername(): string return $this->github_username ?? ''; } + public function hasConnectedGitHubAccount(): bool + { + return ! is_null($this->githubId()); + } + public function hasIdenticon(): bool { return (bool) $this->github_has_identicon; diff --git a/resources/svg/github-line.svg b/resources/svg/github-line.svg new file mode 100644 index 000000000..1a9f70540 --- /dev/null +++ b/resources/svg/github-line.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/views/users/settings/github.blade.php b/resources/views/users/settings/github.blade.php new file mode 100644 index 000000000..20a3c495e --- /dev/null +++ b/resources/views/users/settings/github.blade.php @@ -0,0 +1,55 @@ +@title('GitHub') + +
+
+
+
+

+ GitHub Account +

+ +

+ Connect your GitHub account to keep your profile information in sync. +

+
+ + @if (Auth::user()->hasConnectedGitHubAccount()) +
+ + + + + Disconnect GitHub + + +
+ @else +
+

+ Connecting your GitHub account will automatically populate your GitHub username and use your + GitHub profile image. +

+
+ @endif +
+ + @if (!Auth::user()->hasConnectedGitHubAccount()) + +
+ + Connect GitHub + +
+
+ @endif +
+
diff --git a/resources/views/users/settings/settings.blade.php b/resources/views/users/settings/settings.blade.php index 4ae576947..3ddd9f8ac 100644 --- a/resources/views/users/settings/settings.blade.php +++ b/resources/views/users/settings/settings.blade.php @@ -25,7 +25,13 @@ Password - + + + GitHub + + API Tokens @@ -45,6 +51,7 @@
@include('users.settings.profile') @include('users.settings.password') + @include('users.settings.github') @include('users.settings.api_tokens') @include('users.settings.notification_settings') @include('users.settings.blocked') diff --git a/routes/web.php b/routes/web.php index f8b42d2c4..5c7e652f5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -19,6 +19,7 @@ use App\Http\Controllers\ReplyAbleController; use App\Http\Controllers\ReplyController; use App\Http\Controllers\Settings\ApiTokenController; +use App\Http\Controllers\Settings\GitHubAccountController; use App\Http\Controllers\Settings\NotificationSettingsController; use App\Http\Controllers\Settings\PasswordController; use App\Http\Controllers\Settings\ProfileController as ProfileSettingsController; @@ -79,6 +80,8 @@ Route::put('settings', [ProfileSettingsController::class, 'update'])->name('settings.profile.update'); Route::delete('settings', [ProfileSettingsController::class, 'destroy'])->name('settings.profile.delete'); Route::put('settings/password', [PasswordController::class, 'update'])->name('settings.password.update'); +Route::post('settings/github/connect', [GitHubAccountController::class, 'connect'])->name('settings.github.connect'); +Route::post('settings/github/disconnect', [GitHubAccountController::class, 'disconnect'])->name('settings.github.disconnect'); Route::put('settings/users/{username}/unblock', UnblockUserSettingsController::class)->name('settings.users.unblock'); Route::post('settings/api-tokens', [ApiTokenController::class, 'store'])->name('settings.api-tokens.store'); Route::delete('settings/api-tokens', [ApiTokenController::class, 'destroy'])->name('settings.api-tokens.delete'); From be5948134d82a1654005a9eaf781305e2f2b5c68 Mon Sep 17 00:00:00 2001 From: theHocineSaad Date: Wed, 8 Oct 2025 20:52:36 +0200 Subject: [PATCH 2/6] Add tests for GitHub account connection and disconnection functionality --- tests/Feature/GitHubAccountSettingTest.php | 122 +++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/Feature/GitHubAccountSettingTest.php diff --git a/tests/Feature/GitHubAccountSettingTest.php b/tests/Feature/GitHubAccountSettingTest.php new file mode 100644 index 000000000..e2305d2f2 --- /dev/null +++ b/tests/Feature/GitHubAccountSettingTest.php @@ -0,0 +1,122 @@ +login(); + + $response = $this->actingAs($user)->post('/settings/github/connect'); + + $response->assertRedirect(route('login.github')); + + expect(session('settings.github.connect.intended'))->toBeTrue(); +}); + +test('users can disconnect their GitHub account from settings', function () { + $user = $this->login([ + 'github_id' => '11405387', + 'github_username' => 'theHocineSaad', + 'github_has_identicon' => true, + ]); + + $response = $this->actingAs($user)->post('/settings/github/disconnect'); + + $response->assertRedirect(route('settings.profile')); + $response->assertSessionHas('success', 'Your GitHub account has been disconnected.'); + + $user->refresh(); + + expect($user->github_id)->toBeNull(); + expect($user->github_username)->toBeNull(); + expect($user->github_has_identicon)->toBeFalse(); +}); + +test('users can connect their GitHub account after returning from GitHub', function () { + Queue::fake(); + + $user = $this->login([ + 'github_id' => null, + 'github_username' => null, + ]); + + $socialiteUser = fakeSocialiteUser('11405387', 'theHocineSaad'); + + mockGitHubProvider($socialiteUser); + + $this->withSession(['settings.github.connect.intended' => true]); + + $response = $this->actingAs($user)->get('/auth/github'); + + $response->assertRedirect(route('settings.profile')); + $response->assertSessionHas('success', 'Your GitHub account has been connected.'); + + $user->refresh(); + + expect($user->github_id)->toBe('11405387'); + expect($user->github_username)->toBe('theHocineSaad'); + + Queue::assertPushed(UpdateUserIdenticonStatus::class); +}); + +test('users cannot connect a GitHub account that belongs to another user', function () { + Queue::fake(); + + User::factory()->create([ + 'github_id' => '11405387', + 'github_username' => 'theHocineSaad', + ]); + + $user = $this->login([ + 'github_id' => null, + 'github_username' => null, + ]); + + $socialiteUser = fakeSocialiteUser('11405387', 'theHocineSaad'); + + mockGitHubProvider($socialiteUser); + + $this->withSession(['settings.github.connect.intended' => true]); + + $response = $this->actingAs($user)->get('/auth/github'); + + $response->assertRedirect(route('settings.profile')); + $response->assertSessionHas('error', 'This GitHub account is already connected to another user.'); + + $user->refresh(); + + expect($user->github_id)->toBeNull(); + expect($user->github_username)->toBeNull(); + + Queue::assertNothingPushed(); +}); + +function fakeSocialiteUser(string $id, string $nickname): SocialiteUser +{ + return tap(new SocialiteUser()) + ->setRaw([ + 'id' => $id, + 'login' => $nickname, + ]) + ->map([ + 'id' => $id, + 'nickname' => $nickname, + ]); +} + +function mockGitHubProvider(SocialiteUser $user): void +{ + $provider = Mockery::mock(Provider::class); + $provider->shouldReceive('user')->once()->andReturn($user); + + Socialite::shouldReceive('driver')->once()->with('github')->andReturn($provider); +} From a984f5a3398d301024a7567eaed3e8cbb882ae87 Mon Sep 17 00:00:00 2001 From: theHocineSaad Date: Thu, 9 Oct 2025 16:53:56 +0200 Subject: [PATCH 3/6] Add password check before disconnecting GitHub account --- .../Settings/GitHubAccountController.php | 10 +++++++++- resources/views/users/settings/github.blade.php | 16 +++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Settings/GitHubAccountController.php b/app/Http/Controllers/Settings/GitHubAccountController.php index f9431e4bd..da5c86bde 100644 --- a/app/Http/Controllers/Settings/GitHubAccountController.php +++ b/app/Http/Controllers/Settings/GitHubAccountController.php @@ -23,7 +23,15 @@ public function connect(): RedirectResponse public function disconnect(DisconnectGitHubAccount $disconnectGitHubAccount): RedirectResponse { - $disconnectGitHubAccount(auth()->user()); + $user = auth()->user(); + + if (!$user->password) { + $this->error('You must set a password before disconnecting your GitHub account, otherwise, you will not be able to log in again.'); + + return redirect(route('settings.profile')); + } + + $disconnectGitHubAccount($user); $this->success('Your GitHub account has been disconnected.'); diff --git a/resources/views/users/settings/github.blade.php b/resources/views/users/settings/github.blade.php index 20a3c495e..1467d2730 100644 --- a/resources/views/users/settings/github.blade.php +++ b/resources/views/users/settings/github.blade.php @@ -26,11 +26,17 @@
- - - Disconnect GitHub - - + @if (Auth::user()->password) + + + Disconnect GitHub + + + @else +

+ You must set a password before disconnecting your GitHub account, otherwise, you will not be able to log in again. +

+ @endif @else
From f7a63f6b353294e9a438e0d3160553280fa0e64e Mon Sep 17 00:00:00 2001 From: theHocineSaad Date: Tue, 14 Oct 2025 21:59:08 +0200 Subject: [PATCH 4/6] Use Simple Icons Github icon --- resources/svg/github-line.svg | 1 - resources/views/users/settings/settings.blade.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 resources/svg/github-line.svg diff --git a/resources/svg/github-line.svg b/resources/svg/github-line.svg deleted file mode 100644 index 1a9f70540..000000000 --- a/resources/svg/github-line.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/views/users/settings/settings.blade.php b/resources/views/users/settings/settings.blade.php index 3ddd9f8ac..2c8ff5fba 100644 --- a/resources/views/users/settings/settings.blade.php +++ b/resources/views/users/settings/settings.blade.php @@ -27,7 +27,7 @@ - + GitHub Date: Thu, 16 Oct 2025 10:42:56 +0200 Subject: [PATCH 5/6] wip --- app/Http/Controllers/Auth/GitHubController.php | 1 + app/Http/Controllers/Settings/GitHubAccountController.php | 2 +- resources/views/users/settings/github.blade.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/GitHubController.php b/app/Http/Controllers/Auth/GitHubController.php index cc0a1b033..4d02d142b 100644 --- a/app/Http/Controllers/Auth/GitHubController.php +++ b/app/Http/Controllers/Auth/GitHubController.php @@ -44,6 +44,7 @@ public function handleProviderCallback(ConnectGitHubAccount $connectGitHubAccoun } $isConnectingAttempt = session()->pull('settings.github.connect.intended', false); + if ($isConnectingAttempt) { $currentUser = auth()->user(); $githubId = $socialiteUser->getId(); diff --git a/app/Http/Controllers/Settings/GitHubAccountController.php b/app/Http/Controllers/Settings/GitHubAccountController.php index da5c86bde..8465b62f1 100644 --- a/app/Http/Controllers/Settings/GitHubAccountController.php +++ b/app/Http/Controllers/Settings/GitHubAccountController.php @@ -25,7 +25,7 @@ public function disconnect(DisconnectGitHubAccount $disconnectGitHubAccount): Re { $user = auth()->user(); - if (!$user->password) { + if (! $user->password) { $this->error('You must set a password before disconnecting your GitHub account, otherwise, you will not be able to log in again.'); return redirect(route('settings.profile')); diff --git a/resources/views/users/settings/github.blade.php b/resources/views/users/settings/github.blade.php index 1467d2730..c579ef323 100644 --- a/resources/views/users/settings/github.blade.php +++ b/resources/views/users/settings/github.blade.php @@ -9,7 +9,7 @@

- Connect your GitHub account to keep your profile information in sync. + Connect your GitHub account to keep your profile for easy login and avatar sync.

From 0ea101211f296d9402ead1804690bc8f8ffa2f6f Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 16 Oct 2025 10:51:02 +0200 Subject: [PATCH 6/6] wip --- app/Http/Controllers/Auth/GitHubController.php | 18 +++++++++--------- .../views/users/settings/github.blade.php | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Auth/GitHubController.php b/app/Http/Controllers/Auth/GitHubController.php index 4d02d142b..849779859 100644 --- a/app/Http/Controllers/Auth/GitHubController.php +++ b/app/Http/Controllers/Auth/GitHubController.php @@ -44,23 +44,23 @@ public function handleProviderCallback(ConnectGitHubAccount $connectGitHubAccoun } $isConnectingAttempt = session()->pull('settings.github.connect.intended', false); - + if ($isConnectingAttempt) { $currentUser = auth()->user(); - $githubId = $socialiteUser->getId(); - // Check if the GitHub account is already connected to another user - $existingUser = User::where('github_id', $githubId)->where('id', '!=', $currentUser->id)->first(); + // Check if the GitHub account is already connected to another user. + $existingUser = User::where('github_id', $socialiteUser->getId()) + ->where('id', '!=', $currentUser->id) + ->first(); + if ($existingUser) { $this->error('This GitHub account is already connected to another user.'); + } else { + $connectGitHubAccount($currentUser, $socialiteUser); - return redirect(route('settings.profile')); + $this->success('Your GitHub account has been connected.'); } - $connectGitHubAccount($currentUser, $socialiteUser); - - $this->success('Your GitHub account has been connected.'); - return redirect(route('settings.profile')); } diff --git a/resources/views/users/settings/github.blade.php b/resources/views/users/settings/github.blade.php index c579ef323..bbe4f3523 100644 --- a/resources/views/users/settings/github.blade.php +++ b/resources/views/users/settings/github.blade.php @@ -48,7 +48,7 @@ @endif - @if (!Auth::user()->hasConnectedGitHubAccount()) + @unless (Auth::user()->hasConnectedGitHubAccount())
@@ -56,6 +56,6 @@
- @endif + @endunless