feat: add social_accounts table and provider tracking with verified-email linking

This commit is contained in:
2026-03-19 23:15:01 +01:00
parent 10f612a901
commit 1b9bf0efac
3 changed files with 122 additions and 29 deletions

View File

@@ -3,10 +3,10 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\SocialAccount;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
@@ -63,45 +63,78 @@ class SocialiteController extends Controller
]);
}
// Find existing user by provider ID or email
$user = User::where('email', $socialUser->getEmail())->first();
// First, check if this provider account is already linked to a user
$socialAccount = SocialAccount::where('provider', $provider)
->where('provider_id', $socialUser->getId())
->first();
if (! $user) {
// Create new user if registration is enabled
if (! config('auth-ui.features.registration')) {
if ($socialAccount) {
Auth::login($socialAccount->user, remember: true);
request()->session()->regenerate();
return redirect()->intended(config('auth-ui.redirects.login', '/'));
}
// Check if a user with this email already exists
$existingUser = User::where('email', $socialUser->getEmail())->first();
if ($existingUser) {
if (! $existingUser->hasVerifiedEmail()) {
return redirect()->route('login')->withErrors([
'email' => 'Registration is disabled. Please contact an administrator.',
'email' => 'An account with this email already exists. Please verify your email first, then link your social account.',
]);
}
$name = $socialUser->getName() ?? $socialUser->getNickname() ?? 'User';
$nameParts = explode(' ', $name, 2);
$suggestedUsername = $this->suggestUsername($socialUser);
$existingUser->socialAccounts()->create([
'provider' => $provider,
'provider_id' => $socialUser->getId(),
]);
// Check if username is already taken
if (User::whereRaw('LOWER(username) = ?', [strtolower($suggestedUsername)])->exists()) {
// Store social data in session and redirect to complete profile
session()->put('socialite_user', [
'email' => $socialUser->getEmail(),
'first_name' => $nameParts[0],
'last_name' => $nameParts[1] ?? '',
'suggested_username' => $suggestedUsername,
'provider' => $provider,
]);
Auth::login($existingUser, remember: true);
request()->session()->regenerate();
return redirect()->route('auth.complete-profile');
}
return redirect()->intended(config('auth-ui.redirects.login', '/'));
}
$user = User::create([
'username' => $suggestedUsername,
'first_name' => $nameParts[0],
'last_name' => $nameParts[1] ?? '',
'email' => $socialUser->getEmail(),
'password' => Hash::make(Str::random(24)),
'email_verified_at' => now(),
// New user — check if registration is enabled
if (! config('auth-ui.features.registration')) {
return redirect()->route('login')->withErrors([
'email' => 'Registration is disabled. Please contact an administrator.',
]);
}
$name = $socialUser->getName() ?? $socialUser->getNickname() ?? 'User';
$nameParts = explode(' ', $name, 2);
$suggestedUsername = $this->suggestUsername($socialUser);
// Check if username is already taken
if (User::whereRaw('LOWER(username) = ?', [strtolower($suggestedUsername)])->exists()) {
session()->put('socialite_user', [
'email' => $socialUser->getEmail(),
'first_name' => $nameParts[0],
'last_name' => $nameParts[1] ?? '',
'suggested_username' => $suggestedUsername,
'provider' => $provider,
'provider_id' => $socialUser->getId(),
]);
return redirect()->route('auth.complete-profile');
}
$user = User::create([
'username' => $suggestedUsername,
'first_name' => $nameParts[0],
'last_name' => $nameParts[1] ?? '',
'email' => $socialUser->getEmail(),
]);
$user->forceFill(['email_verified_at' => now()])->save();
$user->socialAccounts()->create([
'provider' => $provider,
'provider_id' => $socialUser->getId(),
]);
Auth::login($user, remember: true);
request()->session()->regenerate();

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SocialAccount extends Model
{
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'user_id',
'provider',
'provider_id',
];
/**
* Get the user that owns the social account.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('social_accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('provider');
$table->string('provider_id');
$table->timestamps();
$table->unique(['provider', 'provider_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('social_accounts');
}
};