diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php
new file mode 100644
index 000000000..7d917e5a6
--- /dev/null
+++ b/app/Http/Controllers/OauthController.php
@@ -0,0 +1,35 @@
+redirect();
+ }
+
+ public function callback(string $provider)
+ {
+ try {
+ $oauthUser = get_socialite_provider($provider)->user();
+ $user = User::whereEmail($oauthUser->email)->first();
+ if (!$user) {
+ $user = User::create([
+ 'name' => $oauthUser->name,
+ 'email' => $oauthUser->email,
+ ]);
+ }
+ Auth::login($user);
+ return redirect('/');
+ } catch (\Exception $e) {
+ ray($e->getMessage());
+ return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
+ }
+ }
+}
diff --git a/app/Livewire/Settings/Auth.php b/app/Livewire/Settings/Auth.php
new file mode 100644
index 000000000..d8bda0569
--- /dev/null
+++ b/app/Livewire/Settings/Auth.php
@@ -0,0 +1,43 @@
+reduce(function($carry, $setting) {
+ $carry["oauth_settings_map.$setting->provider.enabled"] = 'required';
+ $carry["oauth_settings_map.$setting->provider.client_id"] = 'nullable';
+ $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable';
+ $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
+ $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
+ return $carry;
+ }, []);
+ }
+
+ public function mount() {
+ $this->oauth_settings_map = OauthSetting::all()->reduce(function($carry, $setting) {
+ $carry[$setting->provider] = $setting;
+ return $carry;
+ }, []);
+ }
+
+ private function updateOauthSettings() {
+ foreach (array_values($this->oauth_settings_map) as &$setting) {
+ $setting->save();
+ }
+ }
+
+ public function instantSave() {
+ $this->updateOauthSettings();
+ }
+
+ public function submit() {
+ $this->updateOauthSettings();
+ $this->dispatch('success', 'Instance settings updated successfully!');
+ }
+}
diff --git a/app/Models/OauthSetting.php b/app/Models/OauthSetting.php
new file mode 100644
index 000000000..4ab21aeec
--- /dev/null
+++ b/app/Models/OauthSetting.php
@@ -0,0 +1,21 @@
+ empty($value) ? null : Crypt::decryptString($value),
+ set: fn (string | null $value) => empty($value) ? null : Crypt::encryptString($value),
+ );
+ }
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index a9b4496b4..5f95ed37c 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -20,6 +20,9 @@ class EventServiceProvider extends ServiceProvider
// Registered::class => [
// SendEmailVerificationNotification::class,
// ],
+ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
+ \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
+ ],
];
public function boot(): void
{
diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php
index 77de80885..6bb284eef 100644
--- a/app/Providers/FortifyServiceProvider.php
+++ b/app/Providers/FortifyServiceProvider.php
@@ -7,6 +7,7 @@
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\InstanceSettings;
+use App\Models\OauthSetting;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
@@ -56,13 +57,15 @@ public function boot(): void
Fortify::loginView(function () {
$settings = InstanceSettings::get();
+ $enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {
// If there are no users, redirect to registration
return redirect()->route('register');
}
return view('auth.login', [
- 'is_registration_enabled' => $settings->is_registration_enabled
+ 'is_registration_enabled' => $settings->is_registration_enabled,
+ 'enabled_oauth_providers' => $enabled_oauth_providers,
]);
});
diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php
new file mode 100644
index 000000000..0773f6de6
--- /dev/null
+++ b/bootstrap/helpers/socialite.php
@@ -0,0 +1,26 @@
+ $oauth_setting->client_id,
+ 'client_secret' => $oauth_setting->client_secret,
+ 'redirect' => $oauth_setting->redirect_uri,
+ 'tenant' => $oauth_setting->tenant,
+ ];
+ $provider_class_map = [
+ 'azure' => \SocialiteProviders\Azure\Provider::class,
+ 'bitbucket' => \Laravel\Socialite\Two\BitbucketProvider::class,
+ 'github' => \Laravel\Socialite\Two\GithubProvider::class,
+ 'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class,
+ 'google' => \Laravel\Socialite\Two\GoogleProvider::class,
+ ];
+ return Socialite::buildProvider(
+ $provider_class_map[$provider],
+ $config
+ );
+}
diff --git a/composer.json b/composer.json
index f762ed579..1624e1390 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
"laravel/horizon": "^5.15",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1",
+ "laravel/socialite": "^5.12",
"laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0",
@@ -31,6 +32,7 @@
"pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4",
+ "socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3",
"spatie/laravel-ray": "^1.32.4",
diff --git a/composer.lock b/composer.lock
index 74b27137f..347f9f619 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "19b19082b605e09867e6ae65fb8135f6",
+ "content-hash": "9bdaf702cdd870434444f8937a816fdb",
"packages": [
{
"name": "amphp/amp",
@@ -3370,6 +3370,76 @@
},
"time": "2023-11-08T14:08:06+00:00"
},
+ {
+ "name": "laravel/socialite",
+ "version": "v5.12.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/socialite.git",
+ "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/7dae1b072573809f32ab6dcf4aebb57c8b3e8acf",
+ "reference": "7dae1b072573809f32ab6dcf4aebb57c8b3e8acf",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "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"
+ },
+ "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-02-16T08:58:20+00:00"
+ },
{
"name": "laravel/tinker",
"version": "v2.9.0",
@@ -4090,6 +4160,82 @@
],
"time": "2024-01-28T23:22:08+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": "league/uri",
"version": "7.4.0",
@@ -7696,6 +7842,131 @@
],
"time": "2023-10-12T14:38:46+00:00"
},
+ {
+ "name": "socialiteproviders/manager",
+ "version": "v4.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/SocialiteProviders/Manager.git",
+ "reference": "a67f194f0f4c4c7616c549afc697b78df9658d44"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/a67f194f0f4c4c7616c549afc697b78df9658d44",
+ "reference": "a67f194f0f4c4c7616c549afc697b78df9658d44",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0",
+ "laravel/socialite": "^5.2",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.2",
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "SocialiteProviders\\Manager\\ServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SocialiteProviders\\Manager\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Andy Wendt",
+ "email": "andy@awendt.com"
+ },
+ {
+ "name": "Anton Komarev",
+ "email": "a.komarev@cybercog.su"
+ },
+ {
+ "name": "Miguel Piedrafita",
+ "email": "soy@miguelpiedrafita.com"
+ },
+ {
+ "name": "atymic",
+ "email": "atymicq@gmail.com",
+ "homepage": "https://atymic.dev"
+ }
+ ],
+ "description": "Easily add new or override built-in providers in Laravel Socialite.",
+ "homepage": "https://socialiteproviders.com",
+ "keywords": [
+ "laravel",
+ "manager",
+ "oauth",
+ "providers",
+ "socialite"
+ ],
+ "support": {
+ "issues": "https://github.com/socialiteproviders/manager/issues",
+ "source": "https://github.com/socialiteproviders/manager"
+ },
+ "time": "2024-02-17T08:58:03+00:00"
+ },
+ {
+ "name": "socialiteproviders/microsoft-azure",
+ "version": "5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/SocialiteProviders/Microsoft-Azure.git",
+ "reference": "7522b27cd8518706b50e03b40a396fb0a6891feb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/7522b27cd8518706b50e03b40a396fb0a6891feb",
+ "reference": "7522b27cd8518706b50e03b40a396fb0a6891feb",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.2 || ^8.0",
+ "socialiteproviders/manager": "~4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "SocialiteProviders\\Azure\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Hemmings",
+ "email": "chris@hemmin.gs"
+ }
+ ],
+ "description": "Microsoft Azure OAuth2 Provider for Laravel Socialite",
+ "keywords": [
+ "azure",
+ "laravel",
+ "microsoft",
+ "oauth",
+ "provider",
+ "socialite"
+ ],
+ "support": {
+ "docs": "https://socialiteproviders.com/microsoft-azure",
+ "issues": "https://github.com/socialiteproviders/providers/issues",
+ "source": "https://github.com/socialiteproviders/providers"
+ },
+ "time": "2022-03-15T21:17:43+00:00"
+ },
{
"name": "spatie/backtrace",
"version": "1.5.3",
diff --git a/config/app.php b/config/app.php
index 5851640be..b95224fdc 100644
--- a/config/app.php
+++ b/config/app.php
@@ -187,6 +187,7 @@
/*
* Package Service Providers...
*/
+ \SocialiteProviders\Manager\ServiceProvider::class,
/*
* Application Service Providers...
diff --git a/config/services.php b/config/services.php
index 0ace530e8..509e73756 100644
--- a/config/services.php
+++ b/config/services.php
@@ -30,5 +30,4 @@
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
-
];
diff --git a/database/migrations/2024_03_08_180457_nullable_password.php b/database/migrations/2024_03_08_180457_nullable_password.php
new file mode 100644
index 000000000..3bd91b0be
--- /dev/null
+++ b/database/migrations/2024_03_08_180457_nullable_password.php
@@ -0,0 +1,28 @@
+string('password')->nullable()->change();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->string('password')->nullable(false)->change();
+ });
+ }
+};
diff --git a/database/migrations/2024_03_11_150013_create_oauth_settings.php b/database/migrations/2024_03_11_150013_create_oauth_settings.php
new file mode 100644
index 000000000..c2178426d
--- /dev/null
+++ b/database/migrations/2024_03_11_150013_create_oauth_settings.php
@@ -0,0 +1,33 @@
+id();
+ $table->string('provider')->unique();
+ $table->boolean('enabled')->default(false);
+ $table->string('client_id')->nullable();
+ $table->text('client_secret')->nullable();
+ $table->string('redirect_uri')->nullable();
+ $table->string('tenant')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('oauth_settings');
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 2a2767d78..b3fac350f 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -32,6 +32,7 @@ public function run(): void
StandalonePostgresqlSeeder::class,
ScheduledDatabaseBackupSeeder::class,
ScheduledDatabaseBackupExecutionSeeder::class,
+ OauthSettingSeeder::class,
]);
}
}
diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php
new file mode 100644
index 000000000..4d33468c7
--- /dev/null
+++ b/database/seeders/OauthSettingSeeder.php
@@ -0,0 +1,37 @@
+ 0,
+ 'provider' => 'azure',
+ ]);
+ OauthSetting::firstOrCreate([
+ 'id' => 1,
+ 'provider' => 'bitbucket',
+ ]);
+ OauthSetting::firstOrCreate([
+ 'id' => 2,
+ 'provider' => 'github',
+ ]);
+ OauthSetting::firstOrCreate([
+ 'id' => 3,
+ 'provider' => 'gitlab',
+ ]);
+ OauthSetting::firstOrCreate([
+ 'id' => 4,
+ 'provider' => 'google',
+ ]);
+ }
+}
diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php
index 4c66bdabf..206e92f76 100644
--- a/database/seeders/ProductionSeeder.php
+++ b/database/seeders/ProductionSeeder.php
@@ -198,5 +198,8 @@ public function run(): void
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
+
+ $oauth_settings_seeder = new OauthSettingSeeder();
+ $oauth_settings_seeder->run();
}
}
diff --git a/lang/en.json b/lang/en.json
index 1f4fbdb74..368a01c0c 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -1,5 +1,10 @@
{
"auth.login": "Login",
+ "auth.login.azure": "Login with Microsoft",
+ "auth.login.bitbucket": "Login with Bitbucket",
+ "auth.login.github": "Login with GitHub",
+ "auth.login.gitlab": "Login with Gitlab",
+ "auth.login.google": "Login with Google",
"auth.already_registered": "Already registered?",
"auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password",
@@ -10,6 +15,7 @@
"auth.registration_disabled": "Registration is disabled. Please contact the administrator.",
"auth.reset_password": "Reset password",
"auth.failed": "These credentials do not match our records.",
+ "auth.failed.callback": "Failed to process callback from login provider.",
"auth.failed.password": "The provided password is incorrect.",
"auth.failed.email": "We can't find a user with that e-mail address.",
"auth.throttle": "Too many login attempts. Please try again in :seconds seconds.",
diff --git a/resources/css/app.css b/resources/css/app.css
index 1080a84f4..167b7c44a 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -175,3 +175,7 @@ input.input-sm {
option{
@apply text-white;
}
+
+.toast {
+ z-index: 1;
+}
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php
index ed288f9ee..6a013b192 100644
--- a/resources/views/auth/login.blade.php
+++ b/resources/views/auth/login.blade.php
@@ -40,6 +40,11 @@ class="text-xs text-center text-white normal-case bg-transparent border-none rou
@endenv