adds social login

This commit is contained in:
hackerESQ
2024-10-19 23:11:04 -05:00
parent bcb1820095
commit 99c5ad3979
21 changed files with 887 additions and 17 deletions
@@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers;
use Exception;
use App\Models\User;
use App\Http\Controllers\Controller;
use App\Models\ConnectedAccount;
use App\Models\ConnectedAccountVerification;
use App\Notifications\VerifyConnectedAccountNotification;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class SocialLoginController extends Controller
{
/**
* Redirect the user to the GitHub authentication page.
*
*/
public function redirectToProvider(string $provider)
{
$this->validateProvider($provider);
return Socialite::driver($provider)->redirect();
}
/**
* Obtain the user information from GitHub.
*
*/
public function handleProviderCallback(string $provider)
{
$this->validateProvider($provider);
$providerUser = Socialite::driver($provider)->user();
// check if this account is already linked
$connected_account = ConnectedAccount::firstOrNew([
'provider' => $provider,
'provider_id' => $providerUser->id
], [
'token' => $providerUser->token,
'secret' => $providerUser->tokenSecret,
'refresh_token' => $providerUser->refreshToken,
'expires_at' => $providerUser->expiresIn
]);
// already linked, let's go login
if ($connected_account->exists) {
Auth::login($connected_account->user);
return redirect(route('dashboard'));
}
// new user, let's create one
if (!$user = User::where('email', $providerUser->email)->first()) {
$user = User::create([
'name' => $providerUser->name,
'email' => $providerUser->email,
'email_verified_at' => now()
]);
$connected_account->user_id = $user->id;
$connected_account->save();
Auth::login($user);
return redirect(route('dashboard'));
}
// email exists already, send verification link
$verification = ConnectedAccountVerification::updateOrCreate([
'email' => $providerUser->email,
'provider' => $provider,
'verified_at' => null
], [
'provider_id' => $providerUser->id,
'connected_account' => $connected_account
]);
$user->notify(new VerifyConnectedAccountNotification($verification->id));
return redirect(route('login'))
->with('status', __('Account already exists. Check your email for a link to login.'));
}
protected function validateProvider($provider): void
{
if (!in_array($provider, explode(',', config('services.enabled_login_providers')))) {
throw new Exception('Please provide a valid social provider.');
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Http\Controllers\Controller;
use App\Models\ConnectedAccount;
use Illuminate\Support\Facades\Auth;
use App\Models\ConnectedAccountVerification;
class VerifyConnectedAccountController extends Controller
{
public function __invoke(string $verification_id)
{
$verification = ConnectedAccountVerification::findOrFail($verification_id);
if (!$verification->verified_at) {
// mark request as verified
$verification->verified_at = now();
$verification->save();
// mark user as verified
$user = User::where('email', $verification->email)->firstOrFail();
$user->email_verified_at = now();
$user->save();
// add connected account
$user->connectedAccounts()->create([
...$verification->connected_account,
...[
'provider' => $verification->provider,
'provider_id' => $verification->provider_id,
]
]);
Auth::login($user);
}
return redirect(route('dashboard'));
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasTimestamps;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ConnectedAccount extends Model
{
use HasFactory;
use HasTimestamps;
use HasUuids;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'provider',
'provider_id',
'token',
'secret',
'refresh_token',
'expires_at',
];
protected $with = [
'user'
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime',
'expires_at' => 'datetime',
];
}
/**
* Get user of the connected account.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
class ConnectedAccountVerification extends Model
{
use HasUuids;
protected $fillable = [
'provider',
'provider_id',
'email',
'connected_account'
];
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'verified_at' => 'datetime',
'connected_account' => 'json'
];
}
}
+4 -3
View File
@@ -2,8 +2,7 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Traits\HasConnectedAccounts;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Jetstream\HasProfilePhoto;
use Illuminate\Notifications\Notifiable;
@@ -13,8 +12,9 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
class User extends Authenticatable
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens;
use HasFactory;
@@ -23,6 +23,7 @@ class User extends Authenticatable
use TwoFactorAuthenticatable;
use HasUuids;
use HasRelationships;
use HasConnectedAccounts;
protected $fillable = [
'name',
@@ -0,0 +1,59 @@
<?php
namespace App\Notifications;
use App\Models\ConnectedAccountVerification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class VerifyConnectedAccountNotification extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*/
public function __construct(
public string $verification_id
) { }
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
$verification = ConnectedAccountVerification::find($this->verification_id);
$provider = config("services.$verification->provider.name");
return (new MailMessage)
->greeting('Welcome back!')
->subject("Connect your $provider account with Investbrain")
->line("You recently attempted to log into an existing Investbrain account using $provider. To safeguard your Investbrain account, please confirm this was you by pressing the 'Connect $provider' button below:")
->action("Connect $provider", route('verify_connected_account', ['verification_id' => $this->verification_id]))
->line('If you do not recognize this activity, we recommend [changing your password]('.route('profile.show').') as soon as possible. Otherwise, you can disregard this message.');
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Traits;
use App\Models\ConnectedAccount;
use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property Collection $connectedAccounts
*/
trait HasConnectedAccounts
{
/**
* Determine if the user owns the given connected account.
*/
public function ownsConnectedAccount(mixed $connectedAccount): bool
{
return $this->id == optional($connectedAccount)->user_id;
}
/**
* Determine if the user has a specific account type.
*/
public function hasTokenFor(string $provider): bool
{
return $this->connectedAccounts->contains('provider', Str::lower($provider));
}
/**
* Attempt to retrieve the token for a given provider.
*/
public function getTokenFor(string $provider, mixed $default = null): mixed
{
if ($this->hasTokenFor($provider)) {
return $this->connectedAccounts
->where('provider', Str::lower($provider))
->first()
->token;
}
return $default;
}
/**
* Attempt to find a connected account that belongs to the user,
* for the given provider and ID.
*/
public function getConnectedAccountFor(string $provider, string $id): mixed
{
return $this->connectedAccounts
->where('provider', $provider)
->where('provider_id', $id)
->first();
}
/**
* Get all the connected accounts belonging to the user.
*/
public function connectedAccounts(): HasMany
{
return $this->hasMany(ConnectedAccount::class);
}
}