adds social login
This commit is contained in:
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user