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
+11
View File
@@ -7,6 +7,17 @@ APP_URL=http://localhost
ASSET_URL="${APP_URL}" ASSET_URL="${APP_URL}"
APP_PORT=8000 APP_PORT=8000
SELF_HOSTED=true SELF_HOSTED=true
REGISTRATION_ENABLED=true
ENABLED_LOGIN_PROVIDERS=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
@@ -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; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail; use App\Traits\HasConnectedAccounts;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
use Laravel\Jetstream\HasProfilePhoto; use Laravel\Jetstream\HasProfilePhoto;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
@@ -13,8 +12,9 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Staudenmeir\EloquentHasManyDeep\HasRelationships; use Staudenmeir\EloquentHasManyDeep\HasRelationships;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; 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 HasApiTokens;
use HasFactory; use HasFactory;
@@ -23,6 +23,7 @@ class User extends Authenticatable
use TwoFactorAuthenticatable; use TwoFactorAuthenticatable;
use HasUuids; use HasUuids;
use HasRelationships; use HasRelationships;
use HasConnectedAccounts;
protected $fillable = [ protected $fillable = [
'name', '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);
}
}
+1
View File
@@ -10,6 +10,7 @@
"laravel/framework": "^11.9", "laravel/framework": "^11.9",
"laravel/jetstream": "^5.1", "laravel/jetstream": "^5.1",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/socialite": "^5.16",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"livewire/livewire": "^3.5", "livewire/livewire": "^3.5",
"livewire/volt": "^1.6", "livewire/volt": "^1.6",
Generated
+372 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0f0957a51f038034d44aa347d5b97635", "content-hash": "7fcc707e9612c64b40469d52efdb75e6",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@@ -968,6 +968,69 @@
}, },
"time": "2024-09-13T01:29:18+00:00" "time": "2024-09-13T01:29:18+00:00"
}, },
{
"name": "firebase/php-jwt",
"version": "v6.10.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "500501c2ce893c824c801da135d02661199f60c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5",
"reference": "500501c2ce893c824c801da135d02661199f60c5",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.10.1"
},
"time": "2024-05-18T18:05:11+00:00"
},
{ {
"name": "fruitcake/php-cors", "name": "fruitcake/php-cors",
"version": "v1.3.0", "version": "v1.3.0",
@@ -2274,6 +2337,78 @@
}, },
"time": "2024-09-23T13:33:08+00:00" "time": "2024-09-23T13:33:08+00:00"
}, },
{
"name": "laravel/socialite",
"version": "v5.16.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
"reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf",
"reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf",
"shasum": ""
},
"require": {
"ext-json": "*",
"firebase/php-jwt": "^6.4",
"guzzlehttp/guzzle": "^6.0|^7.0",
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"league/oauth1-client": "^1.10.1",
"php": "^7.2|^8.0",
"phpseclib/phpseclib": "^3.0"
},
"require-dev": {
"mockery/mockery": "^1.0",
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.0|^9.3|^10.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Socialite\\SocialiteServiceProvider"
],
"aliases": {
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
}
}
},
"autoload": {
"psr-4": {
"Laravel\\Socialite\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
"homepage": "https://laravel.com",
"keywords": [
"laravel",
"oauth"
],
"support": {
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
"time": "2024-09-03T09:46:57+00:00"
},
{ {
"name": "laravel/tinker", "name": "laravel/tinker",
"version": "v2.10.0", "version": "v2.10.0",
@@ -2716,6 +2851,82 @@
], ],
"time": "2024-09-21T08:32:55+00:00" "time": "2024-09-21T08:32:55+00:00"
}, },
{
"name": "league/oauth1-client",
"version": "v1.10.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth1-client.git",
"reference": "d6365b901b5c287dd41f143033315e2f777e1167"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167",
"reference": "d6365b901b5c287dd41f143033315e2f777e1167",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-openssl": "*",
"guzzlehttp/guzzle": "^6.0|^7.0",
"guzzlehttp/psr7": "^1.7|^2.0",
"php": ">=7.1||>=8.0"
},
"require-dev": {
"ext-simplexml": "*",
"friendsofphp/php-cs-fixer": "^2.17",
"mockery/mockery": "^1.3.3",
"phpstan/phpstan": "^0.12.42",
"phpunit/phpunit": "^7.5||9.5"
},
"suggest": {
"ext-simplexml": "For decoding XML-based responses."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev",
"dev-develop": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\OAuth1\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ben Corlett",
"email": "bencorlett@me.com",
"homepage": "http://www.webcomm.com.au",
"role": "Developer"
}
],
"description": "OAuth 1.0 Client Library",
"keywords": [
"Authentication",
"SSO",
"authorization",
"bitbucket",
"identity",
"idp",
"oauth",
"oauth1",
"single sign on",
"trello",
"tumblr",
"twitter"
],
"support": {
"issues": "https://github.com/thephpleague/oauth1-client/issues",
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1"
},
"time": "2022-04-15T14:02:14+00:00"
},
{ {
"name": "livewire/livewire", "name": "livewire/livewire",
"version": "v3.5.11", "version": "v3.5.11",
@@ -3760,6 +3971,56 @@
}, },
"time": "2024-05-08T12:36:18+00:00" "time": "2024-05-08T12:36:18+00:00"
}, },
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{ {
"name": "phpoffice/phpspreadsheet", "name": "phpoffice/phpspreadsheet",
"version": "1.29.2", "version": "1.29.2",
@@ -3940,6 +4201,116 @@
], ],
"time": "2024-07-20T21:41:07+00:00" "time": "2024-07-20T21:41:07+00:00"
}, },
{
"name": "phpseclib/phpseclib",
"version": "3.0.42",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"shasum": ""
},
"require": {
"paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jim Wigginton",
"email": "terrafrost@php.net",
"role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
"email": "pm@datasphere.ch",
"role": "Developer"
},
{
"name": "Andreas Fischer",
"email": "bantu@phpbb.com",
"role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
"email": "petrich@tronic-media.com",
"role": "Developer"
},
{
"name": "Graham Campbell",
"email": "graham@alt-three.com",
"role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
"homepage": "http://phpseclib.sourceforge.net",
"keywords": [
"BigInteger",
"aes",
"asn.1",
"asn1",
"blowfish",
"crypto",
"cryptography",
"encryption",
"rsa",
"security",
"sftp",
"signature",
"signing",
"ssh",
"twofish",
"x.509",
"x509"
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
},
"funding": [
{
"url": "https://github.com/terrafrost",
"type": "github"
},
{
"url": "https://www.patreon.com/phpseclib",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
"type": "tidelift"
}
],
"time": "2024-09-16T03:06:04+00:00"
},
{ {
"name": "pragmarx/google2fa", "name": "pragmarx/google2fa",
"version": "v8.0.3", "version": "v8.0.3",
+1 -1
View File
@@ -144,7 +144,7 @@ return [
*/ */
'features' => [ 'features' => [
Features::registration(), env('REGISTRATION_ENABLED', true) ? Features::registration() : null,
Features::resetPasswords(), Features::resetPasswords(),
Features::emailVerification(), Features::emailVerification(),
Features::updateProfileInformation(), Features::updateProfileInformation(),
+34
View File
@@ -35,4 +35,38 @@ return [
], ],
], ],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => '/auth/github/callback',
'logo' => 'github-icon',
'color' => '#393939',
'name' => 'GitHub'
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => '/auth/google/callback',
'color' => '#4285F4',
'name' => 'Google'
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => '/auth/facebook/callback',
'color' => '#0165E1',
'name' => 'Facebook'
],
'linkedin-openid' => [
'client_id' => env('LINKEDIN_CLIENT_ID'),
'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
'redirect' => '/auth/linkedin-openid/callback',
'color' => '#0a66c2',
'name' => 'Linkedin'
],
'enabled_login_providers' => env('ENABLED_LOGIN_PROVIDERS', [])
]; ];
@@ -0,0 +1,55 @@
<?php
use App\Models\User;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('password')->nullable()->change();
});
Schema::create('connected_accounts', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignIdFor(User::class, 'user_id')->constrained()->onDelete('cascade');
$table->string('provider');
$table->string('provider_id');
$table->string('token', 1000);
$table->string('secret')->nullable(); // OAuth1
$table->string('refresh_token', 1000)->nullable(); // OAuth2
$table->dateTime('expires_at')->nullable(); // OAuth2
$table->timestamps();
$table->index(['user_id', 'id']);
$table->index(['provider', 'provider_id']);
});
Schema::create('connected_account_verifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('email');
$table->string('provider');
$table->string('provider_id');
$table->json('connected_account');
$table->timestamps();
$table->timestamp('verified_at')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('connected_account_verifications');
Schema::dropIfExists('connected_accounts');
}
};
+3 -1
View File
@@ -51,6 +51,7 @@
"Token Name": "Token Name", "Token Name": "Token Name",
"Permissions": "Permissions", "Permissions": "Permissions",
"Profile Information": "Profile Information", "Profile Information": "Profile Information",
"Account already exists. Check your email for a link to login.": "Account already exists. Check your email for a link to login.",
"Update your account\\'s profile information and email address.": "Update your account\\'s profile information and email address.", "Update your account\\'s profile information and email address.": "Update your account\\'s profile information and email address.",
"Photo": "Photo", "Photo": "Photo",
"Select A New Photo": "Select A New Photo", "Select A New Photo": "Select A New Photo",
@@ -80,7 +81,8 @@
"I agree to the :terms_of_service and :privacy_policy": "I agree to the :terms_of_service and :privacy_policy", "I agree to the :terms_of_service and :privacy_policy": "I agree to the :terms_of_service and :privacy_policy",
"Terms of Service": "Terms of Service", "Terms of Service": "Terms of Service",
"Privacy Policy": "Privacy Notice", "Privacy Policy": "Privacy Notice",
"Need to register?": "Need to register?", "Sign up with email": "Sign up with email",
"Login with": "Login with",
"Already registered?": "Already registered?", "Already registered?": "Already registered?",
"Reset Password": "Reset Password", "Reset Password": "Reset Password",
"Please confirm access to your account by entering the authentication code provided by your authenticator application.": "Please confirm access to your account by entering the authentication code provided by your authenticator application.", "Please confirm access to your account by entering the authentication code provided by your authenticator application.": "Please confirm access to your account by entering the authentication code provided by your authenticator application.",
+3 -1
View File
@@ -51,6 +51,7 @@
"Token Name": "Nombre del Token", "Token Name": "Nombre del Token",
"Permissions": "Permisos", "Permissions": "Permisos",
"Profile Information": "Información del Perfil", "Profile Information": "Información del Perfil",
"Account already exists. Check your email for a link to login.": "La cuenta ya existe. Revisa tu correo electrónico para un enlace de inicio de sesión.",
"Update your account's profile information and email address.": "Actualiza la información de perfil y la dirección de correo electrónico de tu cuenta.", "Update your account's profile information and email address.": "Actualiza la información de perfil y la dirección de correo electrónico de tu cuenta.",
"Photo": "Foto", "Photo": "Foto",
"Select A New Photo": "Seleccionar una Nueva Foto", "Select A New Photo": "Seleccionar una Nueva Foto",
@@ -80,7 +81,8 @@
"I agree to the :terms_of_service and :privacy_policy": "Acepto los :terms_of_service y la :privacy_policy", "I agree to the :terms_of_service and :privacy_policy": "Acepto los :terms_of_service y la :privacy_policy",
"Terms of Service": "Términos de Servicio", "Terms of Service": "Términos de Servicio",
"Privacy Policy": "Aviso de Privacidad", "Privacy Policy": "Aviso de Privacidad",
"Need to register?": "¿Necesitas registrarte?", "Sign up with email": "Regístrate con correo electrónico",
"Login with": "Iniciar sesión con",
"Already registered?": "¿Ya estás registrado?", "Already registered?": "¿Ya estás registrado?",
"Reset Password": "Restablecer Contraseña", "Reset Password": "Restablecer Contraseña",
"Please confirm access to your account by entering the authentication code provided by your authenticator application.": "Por favor, confirma el acceso a tu cuenta ingresando el código de autenticación proporcionado por tu aplicación de autenticación.", "Please confirm access to your account by entering the authentication code provided by your authenticator application.": "Por favor, confirma el acceso a tu cuenta ingresando el código de autenticación proporcionado por tu aplicación de autenticación.",
+26 -7
View File
@@ -47,15 +47,34 @@
</div> </div>
<x-section-border /> @if (\Laravel\Fortify\Features::enabled('registration'))
<div class=""> <x-section-border />
<x-button link="{{ route('register') }}" class="btn-sm btn-block btn-outline btn-secondary" >
{{ __('Need to register?') }}
</x-button>
</div> <div class="">
@foreach(explode(',', config('services.enabled_login_providers')) as $provider)
<x-button
link="{{ route('oauth.redirect', ['provider' => $provider]) }}"
class="btn-sm btn-block my-1"
style='background-color: {{ config("services.$provider.color") }}'
no-wire-navigate
>
@include("components.$provider-icon")
{{ __('Login with') }} {{ config("services.$provider.name") }}
</x-button>
@endforeach
<x-button
link="{{ route('register') }}"
class="btn-sm btn-block btn-outline btn-secondary my-1"
>
{{ __('Sign up with email') }}
</x-button>
</div>
@endif
</form> </form>
</x-authentication-card> </x-authentication-card>
</x-guest-layout> </x-guest-layout>
@@ -0,0 +1,6 @@
<svg viewBox="0 0 90 90" aria-hidden="true" class="size-6 fill-current">
<path fill-rule="evenodd" clip-rule="evenodd" d="M72,12L18,12C14.685,12 12,14.685 12,18L12,72C12,75.315 14.685,78 18,78L48,78L48,51L39,51L39,42L48,42L48,37.167C48,28.017 52.458,24 60.063,24C63.705,24 65.631,24.27 66.543,24.393L66.543,33L61.356,33C58.128,33 57,34.704 57,38.154L57,42L66.462,42L65.178,51L57,51L57,78L72,78C75.315,78 78,75.315 78,72L78,18C78,14.685 75.312,12 72,12Z" />
</svg>

After

Width:  |  Height:  |  Size: 472 B

@@ -1 +1,3 @@
<svg viewBox="0 0 24 24" aria-hidden="true" class="size-6 fill-current"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.607 9.607 0 0 1 12 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48 3.97-1.32 6.833-5.054 6.833-9.458C22 6.463 17.522 2 12 2Z"></path></svg> <svg viewBox="0 0 24 24" aria-hidden="true" class="size-6 fill-current">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.607 9.607 0 0 1 12 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48 3.97-1.32 6.833-5.054 6.833-9.458C22 6.463 17.522 2 12 2Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 871 B

@@ -0,0 +1,6 @@
<svg viewBox="0 0 52 52" aria-hidden="true" class="size-6 fill-current">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.996,48C13.313,48 2.992,37.684 2.992,25C2.992,12.316 13.313,2 25.996,2C31.742,2 37.242,4.129 41.488,7.996L42.262,8.703L34.676,16.289L33.973,15.688C31.746,13.781 28.914,12.73 25.996,12.73C19.23,12.73 13.723,18.234 13.723,25C13.723,31.766 19.23,37.27 25.996,37.27C30.875,37.27 34.73,34.777 36.547,30.531L24.996,30.531L24.996,20.176L47.547,20.207L47.715,21C48.891,26.582 47.949,34.793 43.184,40.668C39.238,45.531 33.457,48 25.996,48Z" />
</svg>

After

Width:  |  Height:  |  Size: 582 B

@@ -0,0 +1,3 @@
<svg viewBox="0 0 48 48" aria-hidden="true" class="size-6 fill-current">
<path fill-rule="evenodd" clip-rule="evenodd" d="M42,37C42,39.762 39.762,42 37,42L11,42C8.239,42 6,39.762 6,37L6,11C6,8.238 8.239,6 11,6L37,6C39.762,6 42,8.238 42,11L42,37ZM36,36L35.999,26.274C35.999,21.25 33.316,19 29.738,19C26.85,19 25.721,20.5 25,21.616L25,19L20,19L20,36L25,36L25,27C25,26.511 24.957,25.543 25.101,25.193C25.495,24.215 26.307,23.203 27.808,23.203C29.775,23.203 31,24.703 31,26.901L31,36L36,36ZM12,19L12,36L17,36L17,19L12,19ZM14.485,17C16.035,17 17,15.887 17,14.499C16.972,13.08 16.035,12 14.514,12C12.995,12 12,13.08 12,14.499C12,15.888 12.965,17 14.457,17L14.485,17Z" />
</svg>

After

Width:  |  Height:  |  Size: 676 B

+10 -2
View File
@@ -4,7 +4,9 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HoldingController; use App\Http\Controllers\HoldingController;
use App\Http\Controllers\DashboardController; use App\Http\Controllers\DashboardController;
use App\Http\Controllers\PortfolioController; use App\Http\Controllers\PortfolioController;
use App\Http\Controllers\SocialLoginController;
use App\Http\Controllers\TransactionController; use App\Http\Controllers\TransactionController;
use App\Http\Controllers\VerifyConnectedAccountController;
use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController; use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController;
use Laravel\Jetstream\Http\Controllers\Livewire\TermsOfServiceController; use Laravel\Jetstream\Http\Controllers\Livewire\TermsOfServiceController;
@@ -21,10 +23,10 @@ Route::get('/test', function () {
// //
}); });
Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified'])->group(function () { Route::middleware(['auth:sanctum', config('jetstream.auth_session')])->group(function () {
Route::get('/dashboard', [DashboardController::class, 'show'])->name('dashboard'); Route::get('/dashboard', [DashboardController::class, 'show'])->name('dashboard');
Route::view('/import-export', 'import-export')->name('import-export'); Route::view('/import-export', 'import-export')->name('import-export')->middleware('verified');
Route::get('/portfolio/create', [PortfolioController::class, 'create'])->name('portfolio.create'); Route::get('/portfolio/create', [PortfolioController::class, 'create'])->name('portfolio.create');
Route::get('/portfolio/{portfolio}', [PortfolioController::class, 'show'])->name('portfolio.show'); Route::get('/portfolio/{portfolio}', [PortfolioController::class, 'show'])->name('portfolio.show');
@@ -37,3 +39,9 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
// overwrites jetstream routes // overwrites jetstream routes
Route::get('/terms', [TermsOfServiceController::class, 'show'])->name('terms.show'); Route::get('/terms', [TermsOfServiceController::class, 'show'])->name('terms.show');
Route::get('/privacy', [PrivacyPolicyController::class, 'show'])->name('policy.show'); Route::get('/privacy', [PrivacyPolicyController::class, 'show'])->name('policy.show');
// social login routes
Route::get('auth/verify/{verification_id}', VerifyConnectedAccountController::class)->name('verify_connected_account');
Route::get('auth/{provider}', [SocialLoginController::class, 'redirectToProvider'])->name('oauth.redirect');
Route::get('auth/{provider}/callback', [SocialLoginController::class, 'handleProviderCallback']);