Skip to content

Commit 5a72056

Browse files
Log in using GitHub
1 parent 07799ac commit 5a72056

File tree

13 files changed

+559
-42
lines changed

13 files changed

+559
-42
lines changed

.env.example

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ LEMON_SQUEEZY_PATH=
6565
GITHUB_PERSONAL_ACCESS_TOKEN=
6666
GITHUB_CLIENT_ID=
6767
GITHUB_CLIENT_SECRET=
68-
GITHUB_REDIRECT_URI=
6968

7069
SPONSORS_GITHUB_USERNAMES=
7170
SPONSORS_GITHUB_COMPANY_USERNAMES=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers\Auth;
6+
7+
use App\Models\User;
8+
use App\Services\GitHub;
9+
use Illuminate\Http\RedirectResponse;
10+
use Illuminate\Http\Request;
11+
use Illuminate\Support\Facades\Auth;
12+
use Laravel\Socialite\Facades\Socialite;
13+
use Laravel\Socialite\Two\GithubProvider;
14+
15+
final readonly class GitHubLoginController
16+
{
17+
/**
18+
* Handles the GitHub login redirect.
19+
*/
20+
public function index(): RedirectResponse
21+
{
22+
/** @var GithubProvider $driver */
23+
$driver = Socialite::driver('github');
24+
25+
return $driver->redirectUrl(route('profile.connect.github.login.callback'))->redirect();
26+
}
27+
28+
/**
29+
* Handles the GitHub login callback.
30+
*/
31+
public function update(Request $request, GitHub $github): RedirectResponse
32+
{
33+
$githubUser = Socialite::driver('github')->user();
34+
35+
$user = User::where('email', $githubUser->getEmail())->first();
36+
if (! $user instanceof User) {
37+
session([
38+
'github_email' => $githubUser->getEmail(),
39+
'github_username' => $githubUser->getNickname(),
40+
]);
41+
42+
return to_route('register');
43+
}
44+
if ($user->github_username === null) {
45+
$errors = $github->linkGitHubUser($githubUser->getNickname(), $user, $request);
46+
47+
if ($errors !== []) {
48+
return to_route('login')->withErrors($errors, 'github');
49+
}
50+
}
51+
Auth::login($user);
52+
53+
return to_route('home.feed');
54+
}
55+
}

app/Http/Controllers/Auth/RegisteredUserController.php

+28-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
use App\Rules\Recaptcha;
1010
use App\Rules\UnauthorizedEmailProviders;
1111
use App\Rules\Username;
12+
use App\Services\GitHub;
1213
use Illuminate\Auth\Events\Registered;
1314
use Illuminate\Http\RedirectResponse;
1415
use Illuminate\Http\Request;
16+
use Illuminate\Support\Carbon;
1517
use Illuminate\Support\Facades\Auth;
1618
use Illuminate\Support\Facades\Hash;
1719
use Illuminate\Validation\Rules;
@@ -24,16 +26,27 @@
2426
*/
2527
public function create(): View
2628
{
27-
return view('auth.register');
29+
$githubEmail = session()->pull('github_email');
30+
$githubUsername = session()->pull('github_username');
31+
32+
return view('auth.register')->with('githubEmail', $githubEmail)->with('githubUsername', $githubUsername);
2833
}
2934

3035
/**
3136
* Handle an incoming registration request.
3237
*
3338
* @throws \Illuminate\Validation\ValidationException
3439
*/
35-
public function store(Request $request): RedirectResponse
40+
public function store(Request $request, GitHub $gitHub): RedirectResponse
3641
{
42+
$githubUsername = $request->github_username;
43+
if ($githubUsername) {
44+
session([
45+
'github_email' => $request->email,
46+
'github_username' => $request->github_username,
47+
]);
48+
}
49+
3750
$request->validate([
3851
'name' => ['required', 'string', 'max:255'],
3952
'username' => ['required', 'string', 'min:4', 'max:50', 'unique:'.User::class, new Username],
@@ -43,13 +56,26 @@ public function store(Request $request): RedirectResponse
4356
'g-recaptcha-response' => app()->environment('production') ? ['required', new Recaptcha($request->ip())] : [],
4457
]);
4558

59+
$request->session()->forget(['github_email', 'github_username']);
60+
4661
$user = User::create([
4762
'name' => $request->name,
4863
'email' => $request->email,
4964
'username' => $request->username,
5065
'password' => Hash::make($request->string('password')->value()),
5166
]);
5267

68+
if (is_string($githubUsername) && $githubUsername !== '') {
69+
$errors = $gitHub->linkGitHubUser($githubUsername, $user, $request);
70+
if ($errors !== []) {
71+
$user->delete();
72+
73+
return to_route('register')->withErrors($errors, 'github');
74+
}
75+
$user->email_verified_at = Carbon::now();
76+
$user->save();
77+
}
78+
5379
event(new Registered($user));
5480

5581
Auth::login($user);

app/Http/Controllers/UserGitHubUsernameController.php

+14-35
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
namespace App\Http\Controllers;
66

77
use App\Jobs\SyncVerifiedUser;
8-
use App\Jobs\UpdateUserAvatar;
98
use App\Models\User;
9+
use App\Services\GitHub;
1010
use Illuminate\Http\RedirectResponse;
1111
use Illuminate\Http\Request;
12-
use Illuminate\Support\Facades\Validator;
13-
use Illuminate\Validation\ValidationException;
1412
use Laravel\Socialite\Facades\Socialite;
13+
use Laravel\Socialite\Two\GithubProvider;
1514

1615
final readonly class UserGitHubUsernameController
1716
{
@@ -20,51 +19,31 @@
2019
*/
2120
public function index(): RedirectResponse
2221
{
23-
return type(Socialite::driver('github')->redirect())->as(RedirectResponse::class);
22+
/** @var GithubProvider $driver */
23+
$driver = Socialite::driver('github');
24+
25+
return $driver->redirectUrl(route('profile.connect.github.update'))->redirect();
2426
}
2527

2628
/**
2729
* Handles the GitHub connection update.
2830
*/
29-
public function update(Request $request): RedirectResponse
31+
public function update(Request $request, GitHub $github): RedirectResponse
3032
{
3133
$githubUser = Socialite::driver('github')->user();
3234

3335
$user = type($request->user())->as(User::class);
3436

35-
try {
36-
$validated = Validator::validate([
37-
'github_username' => $githubUser->getNickname(),
38-
], [
39-
'github_username' => ['required', 'string', 'max:255', 'unique:users,github_username'],
40-
], [
41-
'github_username.unique' => 'This GitHub username is already connected to another account.',
42-
]);
43-
} catch (ValidationException $e) {
44-
if ($githubUser->getNickname() === $user->github_username) {
45-
session()->flash('flash-message', 'The same GitHub account has been connected.');
46-
47-
return to_route('profile.edit');
48-
}
49-
50-
return to_route('profile.edit')->withErrors($e->errors(), 'verified');
51-
}
37+
$githubUsername = $githubUser->getNickname();
5238

53-
$user->update($validated);
39+
$errors = $github->linkGitHubUser($githubUsername, $user, $request);
5440

55-
SyncVerifiedUser::dispatchSync($user);
56-
57-
$user = type($user->fresh())->as(User::class);
58-
59-
$user->is_verified
60-
? session()->flash('flash-message', 'Your GitHub account has been connected and you are now verified.')
61-
: session()->flash('flash-message', 'Your GitHub account has been connected.');
41+
if ($errors !== []) {
42+
if ($githubUsername === $user->github_username) {
43+
session()->flash('flash-message', 'The same GitHub account has been connected.');
44+
}
6245

63-
if (! $user->is_uploaded_avatar) {
64-
UpdateUserAvatar::dispatch(
65-
user: $user,
66-
service: 'github',
67-
);
46+
return to_route('profile.edit')->withErrors($errors, 'verified');
6847
}
6948

7049
return to_route('profile.edit');

app/Services/GitHub.php

+55
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
namespace App\Services;
66

77
use App\Exceptions\GitHubException;
8+
use App\Jobs\SyncVerifiedUser;
9+
use App\Jobs\UpdateUserAvatar;
10+
use App\Models\User;
11+
use Illuminate\Http\Request;
812
use Illuminate\Support\Facades\Http;
13+
use Illuminate\Support\Facades\Validator;
14+
use Illuminate\Validation\ValidationException;
915

1016
final readonly class GitHub
1117
{
@@ -41,6 +47,55 @@ public function isCompanySponsor(string $username): bool
4147
)->values()->isNotEmpty();
4248
}
4349

50+
/**
51+
* Validates the user received from Github and links our user to it
52+
*
53+
* @return array<string, mixed> list of validation errors
54+
*/
55+
public function linkGitHubUser(?string $githubUsername, User $user, Request $request): array
56+
{
57+
try {
58+
$validated = $this->validateGitHubUsername($githubUsername);
59+
} catch (ValidationException $e) {
60+
return $e->errors();
61+
}
62+
63+
$user->update($validated);
64+
65+
SyncVerifiedUser::dispatchSync($user);
66+
67+
$user = type($user->fresh())->as(User::class);
68+
69+
$user->is_verified
70+
? $request->session()->flash('flash-message', 'Your GitHub account has been connected and you are now verified.')
71+
: $request->session()->flash('flash-message', 'Your GitHub account has been connected.');
72+
73+
if (! $user->is_uploaded_avatar) {
74+
UpdateUserAvatar::dispatch(
75+
user: $user,
76+
service: 'github',
77+
);
78+
}
79+
80+
return [];
81+
}
82+
83+
/**
84+
* @return array<string, mixed>
85+
*
86+
* @throws ValidationException
87+
*/
88+
private function validateGitHubUsername(?string $githubUsername): array
89+
{
90+
return Validator::validate([
91+
'github_username' => $githubUsername,
92+
], [
93+
'github_username' => ['required', 'string', 'max:255', 'unique:users,github_username'],
94+
], [
95+
'github_username.unique' => 'This GitHub username is already connected to another account.',
96+
]);
97+
}
98+
4499
/**
45100
* Get the content from the GitHub API.
46101
*

config/services.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
'github' => [
4242
'client_id' => env('GITHUB_CLIENT_ID'),
4343
'client_secret' => env('GITHUB_CLIENT_SECRET'),
44-
'redirect' => env('GITHUB_REDIRECT_URI'),
44+
'redirect' => '',
4545
'token' => env('GITHUB_PERSONAL_ACCESS_TOKEN'),
4646
],
4747
];

resources/views/auth/login.blade.php

+16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
<x-guest-layout>
2+
<x-input-error
3+
:messages="$errors->github->get('github_username')"
4+
class="mb-5"
5+
/>
6+
27
<form
38
method="POST"
49
action="{{ route('login') }}"
@@ -91,6 +96,17 @@ class="text-sm dark:text-slate-200 text-slate-800 underline hover:no-underline"
9196
</div>
9297
</form>
9398

99+
<div class="mt-4 text-center">
100+
<a
101+
href="{{ route('profile.connect.github.login') }}"
102+
>
103+
<span class="inline-flex items-center gap-x-2 text-sm dark:text-slate-200 text-slate-800 underline hover:no-underline">
104+
<x-icons.github class="inline-block h-6 w-6 mr-100" />
105+
{{ __('Log in using GitHub') }}
106+
</span>
107+
</a>
108+
</div>
109+
94110
<x-section-border />
95111

96112
<div class="mt-4 text-center text-sm text-slate-500">

resources/views/auth/register.blade.php

+23-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66
></script>
77
@endsection
88

9+
<x-input-error
10+
:messages="$errors->github->get('github_username')"
11+
class="mb-5"
12+
/>
13+
14+
@if ($githubUsername)
15+
<div class="mb-5">
16+
{{ __('To complete your registration using GitHub, please fill in these additional fields:') }}
17+
</div>
18+
@endif
19+
920
<form
1021
method="POST"
1122
action="{{ route('register') }}"
@@ -61,11 +72,12 @@ class="mt-2"
6172
/>
6273
<x-text-input
6374
id="email"
64-
class="mt-1 block w-full"
75+
class="mt-1 block w-full {{ $githubEmail !== null ? 'text-slate-500' : '' }}"
6576
type="email"
6677
name="email"
67-
:value="old('email')"
78+
:value="old('email', $githubEmail)"
6879
required
80+
:readonly="$githubEmail !== null"
6981
autocomplete="email"
7082
/>
7183
<x-input-error
@@ -162,6 +174,15 @@ class="mt-2"
162174
/>
163175
@endif
164176

177+
@if ($githubUsername)
178+
<x-text-input
179+
id="github_username"
180+
name="github_username"
181+
type="hidden"
182+
:value="$githubUsername"
183+
/>
184+
@endif
185+
165186
<div class="mt-4 flex items-center justify-end space-x-3.5 text-sm">
166187
<div>
167188
<span class="text-slate-500">Already have an account?</span>

routes/auth.php

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Http\Controllers\Auth\ConfirmablePasswordController;
77
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
88
use App\Http\Controllers\Auth\EmailVerificationPromptController;
9+
use App\Http\Controllers\Auth\GitHubLoginController;
910
use App\Http\Controllers\Auth\NewPasswordController;
1011
use App\Http\Controllers\Auth\PasswordResetLinkController;
1112
use App\Http\Controllers\Auth\RegisteredUserController;
@@ -47,6 +48,15 @@
4748
->middleware('throttle:two-factor');
4849
});
4950

51+
Route::prefix('/profile/connect/github')->group(function () {
52+
Route::get('/login', [GitHubLoginController::class, 'index'])
53+
->name('profile.connect.github.login');
54+
55+
Route::get('/login/callback', [
56+
GitHubLoginController::class, 'update',
57+
])->name('profile.connect.github.login.callback');
58+
});
59+
5060
Route::middleware('auth')->group(function () {
5161
Route::get('verify-email', EmailVerificationPromptController::class)
5262
->name('verification.notice');

0 commit comments

Comments
 (0)