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}"
APP_PORT=8000
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_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;
// 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);
}
}
+1
View File
@@ -10,6 +10,7 @@
"laravel/framework": "^11.9",
"laravel/jetstream": "^5.1",
"laravel/sanctum": "^4.0",
"laravel/socialite": "^5.16",
"laravel/tinker": "^2.9",
"livewire/livewire": "^3.5",
"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",
"This file is @generated automatically"
],
"content-hash": "0f0957a51f038034d44aa347d5b97635",
"content-hash": "7fcc707e9612c64b40469d52efdb75e6",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -968,6 +968,69 @@
},
"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",
"version": "v1.3.0",
@@ -2274,6 +2337,78 @@
},
"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",
"version": "v2.10.0",
@@ -2716,6 +2851,82 @@
],
"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",
"version": "v3.5.11",
@@ -3760,6 +3971,56 @@
},
"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",
"version": "1.29.2",
@@ -3940,6 +4201,116 @@
],
"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",
"version": "v8.0.3",
+1 -1
View File
@@ -144,7 +144,7 @@ return [
*/
'features' => [
Features::registration(),
env('REGISTRATION_ENABLED', true) ? Features::registration() : null,
Features::resetPasswords(),
Features::emailVerification(),
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",
"Permissions": "Permissions",
"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.",
"Photo": "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",
"Terms of Service": "Terms of Service",
"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?",
"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.",
+3 -1
View File
@@ -51,6 +51,7 @@
"Token Name": "Nombre del Token",
"Permissions": "Permisos",
"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.",
"Photo": "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",
"Terms of Service": "Términos de Servicio",
"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?",
"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.",
+25 -6
View File
@@ -47,15 +47,34 @@
</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 class="">
</div>
@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>
</x-authentication-card>
</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\DashboardController;
use App\Http\Controllers\PortfolioController;
use App\Http\Controllers\SocialLoginController;
use App\Http\Controllers\TransactionController;
use App\Http\Controllers\VerifyConnectedAccountController;
use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController;
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::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/{portfolio}', [PortfolioController::class, 'show'])->name('portfolio.show');
@@ -37,3 +39,9 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
// overwrites jetstream routes
Route::get('/terms', [TermsOfServiceController::class, 'show'])->name('terms.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']);