diff --git a/docs/docs/hosting/hub-setup.mdx b/docs/docs/hosting/hub-setup.mdx index 6c4829222..c08e52790 100644 --- a/docs/docs/hosting/hub-setup.mdx +++ b/docs/docs/hosting/hub-setup.mdx @@ -31,3 +31,15 @@ In `sourcecode/hub`, or the `hub` Docker container: ```sh php artisan edlib:create-admin-user your@email.example ``` + +## Configuration settings + +### `FEATURE_SOCIAL_USERS_ARE_VERIFIED` + +If enabled, users logging in from "social login providers" (i.e. Facebook) will +automatically have their email addresses verified upon next login, without +having to go through the email verification process. + +:::caution +Only enable this if you trust a login provider completely. +::: diff --git a/sourcecode/hub/.env.example b/sourcecode/hub/.env.example index 30305c8c0..b5724f492 100644 --- a/sourcecode/hub/.env.example +++ b/sourcecode/hub/.env.example @@ -10,6 +10,7 @@ ASSET_URL=null FEATURE_SIGNUP_ENABLED=true FEATURE_RESET_PASSWORD_ENABLED=true FEATURE_NOINDEX=false +FEATURE_SOCIAL_USERS_ARE_VERIFIED=false LOG_CHANNEL=stack LOG_DEPRECATIONS_CHANNEL=null diff --git a/sourcecode/hub/app/Configuration/Features.php b/sourcecode/hub/app/Configuration/Features.php index bb1559afa..e02e12b67 100644 --- a/sourcecode/hub/app/Configuration/Features.php +++ b/sourcecode/hub/app/Configuration/Features.php @@ -28,4 +28,9 @@ public function isNoindexEnabled(): bool { return $this->enabled('noindex'); } + + public function socialUsersAreVerified(): bool + { + return $this->enabled('social-users-are-verified'); + } } diff --git a/sourcecode/hub/app/Models/User.php b/sourcecode/hub/app/Models/User.php index df31e1f1a..04a7d36cd 100644 --- a/sourcecode/hub/app/Models/User.php +++ b/sourcecode/hub/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; +use App\Configuration\Features; use App\Events\UserSaved; use BadMethodCallException; use Database\Factories\UserFactory; @@ -153,25 +154,31 @@ public static function fromSocial(string $provider, SocialiteUser $details): sel throw new InvalidArgumentException('Unknown social provider'); } + $features = app()->make(Features::class); + $user = self::firstWhere("{$provider}_id", $details->getId()); - if ($user) { - return $user; + if (!$user) { + $user = self::firstWhere('email', $details->getEmail()); } - $user = self::firstWhere('email', $details->getEmail()); - if ($user) { $user->forceFill(["{$provider}_id" => $details->getId()]); $user->save(); + } else { + $user = self::forceCreate([ + 'name' => $details->getName(), + 'email' => $details->getEmail(), + "{$provider}_id" => $details->getId(), + 'email_verified' => $features->socialUsersAreVerified(), + ]); + } - return $user; + if (!$user->email_verified && $features->socialUsersAreVerified()) { + $user->email_verified = true; + $user->save(); } - return self::forceCreate([ - 'name' => $details->getName(), - 'email' => $details->getEmail(), - "{$provider}_id" => $details->getId(), - ]); + return $user; } } diff --git a/sourcecode/hub/config/features.php b/sourcecode/hub/config/features.php index 1cc95d534..b29367008 100644 --- a/sourcecode/hub/config/features.php +++ b/sourcecode/hub/config/features.php @@ -8,4 +8,6 @@ 'forgot-password' => (bool) env('FEATURE_RESET_PASSWORD_ENABLED', true), 'noindex' => (bool) env('FEATURE_NOINDEX', false), + + 'social-users-are-verified' => (bool) env('FEATURE_SOCIAL_USERS_ARE_VERIFIED', false), ]; diff --git a/sourcecode/hub/tests/Feature/UserTest.php b/sourcecode/hub/tests/Feature/UserTest.php index a83994379..aa080c2b4 100644 --- a/sourcecode/hub/tests/Feature/UserTest.php +++ b/sourcecode/hub/tests/Feature/UserTest.php @@ -9,6 +9,7 @@ use Tests\Stub\SocialiteUser; use Tests\TestCase; +use function config; use function json_decode; use const JSON_THROW_ON_ERROR; @@ -72,6 +73,7 @@ public function testFindsSocialAccountById(): void $user = User::factory()->create([ 'name' => 'Not From Details', 'email' => 'something@different', + 'email_verified' => false, 'google_id' => $details->getId(), ]); @@ -81,6 +83,7 @@ public function testFindsSocialAccountById(): void $this->assertSame($details->getId(), $found->google_id); $this->assertSame('Not From Details', $found->name); $this->assertSame('something@different', $found->email); + $this->assertFalse($found->email_verified); } public function testFindsSocialAccountByEmail(): void @@ -90,6 +93,7 @@ public function testFindsSocialAccountByEmail(): void $user = User::factory()->create([ 'name' => 'Not From Details', 'email' => $details->getEmail(), + 'email_verified' => false, 'facebook_id' => 'non-matching', ]); @@ -99,6 +103,7 @@ public function testFindsSocialAccountByEmail(): void $this->assertSame($details->getId(), $found->facebook_id); $this->assertSame('Not From Details', $found->name); $this->assertSame($details->getEmail(), $found->email); + $this->assertFalse($found->email_verified); } public function testCreatesSocialAccount(): void @@ -111,5 +116,35 @@ public function testCreatesSocialAccount(): void $this->assertSame($details->getId(), $created->auth0_id); $this->assertSame($details->getName(), $created->name); $this->assertSame($details->getEmail(), $created->email); + $this->assertFalse($created->email_verified); + } + + public function testCreatedSocialAccountIsVerifiedWithSettingEnabled(): void + { + config(['features.social-users-are-verified' => true]); + + $details = new SocialiteUser(); + + $created = User::fromSocial('auth0', $details); + + $this->assertTrue($created->email_verified); + } + + public function testFoundSocialAccountIsVerifiedWithSettingEnabled(): void + { + config(['features.social-users-are-verified' => true]); + + $details = new SocialiteUser(); + + User::factory()->create([ + 'auth0_id' => $details->getId(), + 'email' => $details->getEmail(), + 'email_verified' => false, + ]); + + $found = User::fromSocial('auth0', $details); + + $this->assertFalse($found->wasRecentlyCreated); + $this->assertTrue($found->email_verified); } }