From 5fd7191d4abb07797866ec642d22be1711ce344c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:07:54 +0200 Subject: [PATCH] fix(auth): allow OAuth sign-up with an unverified provider email The provider email verification check now runs only when an account with that email already exists. A new account follows the registration settings, the same way password registration does, which also doesn't verify the email. Linking to an existing account still needs a verified email, except for password-less users created before OAuth identities. Add tests for new-user registration from Authentik, Discord and GitLab when the provider email is unverified. Also test that the "Registration is disabled" error is shown when registration is turned off. --- app/Services/Auth/OauthLoginService.php | 13 ++++---- tests/Feature/OauthControllerTest.php | 44 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app/Services/Auth/OauthLoginService.php b/app/Services/Auth/OauthLoginService.php index c63bc15353..a1522d0f1d 100644 --- a/app/Services/Auth/OauthLoginService.php +++ b/app/Services/Auth/OauthLoginService.php @@ -132,13 +132,14 @@ class OauthLoginService $user = User::whereEmail($email)->first(); - // Before OAuth identities existed, OAuth sign-in matched users by email only. - // Users from that time keep signing in without email delivery, so their - // first identity links without a provider verification claim. A user who - // has a password never gets this exemption. This check runs first, so an - // unverified email cannot reveal a linked account. + // Linking to an existing account needs a verified email. A new account + // follows the registration settings, like password registration, which + // does not verify the email either. Password-less users from before OAuth + // identities existed (OAuth matched by email only) link their first + // identity without a verification claim. This check runs before the + // linked-account check, so an unverified email cannot reveal one. $isPreUpgradeOauthUser = $user?->created_before_oauth_identities === true && ! $user->hasPassword(); - if (! $isPreUpgradeOauthUser && ! $this->hasVerifiedEmail($provider, $rawClaims, $email)) { + if ($user && ! $isPreUpgradeOauthUser && ! $this->hasVerifiedEmail($provider, $rawClaims, $email)) { throw new OauthLoginException('OAuth provider did not verify the email address', 'auth.failed.oauth_email_unverified'); } diff --git a/tests/Feature/OauthControllerTest.php b/tests/Feature/OauthControllerTest.php index e321f1bc7a..8ac1ffcaf3 100644 --- a/tests/Feature/OauthControllerTest.php +++ b/tests/Feature/OauthControllerTest.php @@ -878,3 +878,47 @@ it('allows OAuth user creation through global registration or the OIDC user crea 'oidc, registration off, oidc user creation off' => ['oidc', false, false, false], 'oidc, registration on, oidc user creation off' => ['oidc', true, false, true], ]); + +it('registers a new user from an unverified provider email when registration is enabled', function (string $provider, array $rawClaims) { + InstanceSettings::findOrFail(0)->update(['is_registration_enabled' => true]); + $setting = OauthSetting::updateOrCreate(['provider' => $provider], [ + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'base_url' => 'https://auth.example.com', + 'enabled' => true, + ]); + + $user = app(OauthLoginService::class)->login($provider, (object) [ + 'email' => 'new-user@example.com', + 'name' => 'New User', + 'id' => 'new-provider-id', + 'user' => $rawClaims, + ], $setting); + + expect($user->email)->toBe('new-user@example.com') + ->and(OauthIdentity::where(['user_id' => $user->id, 'provider' => $provider])->exists())->toBeTrue(); + $this->assertAuthenticatedAs($user); +})->with([ + 'authentik default email scope' => ['authentik', ['email_verified' => false]], + 'discord unverified' => ['discord', ['verified' => false]], + 'gitlab unconfirmed' => ['gitlab', ['confirmed_at' => null]], +]); + +it('tells a new user with an unverified provider email that registration is disabled', function () { + $setting = OauthSetting::updateOrCreate(['provider' => 'authentik'], [ + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'base_url' => 'https://auth.example.com', + 'enabled' => true, + ]); + + expect(fn () => app(OauthLoginService::class)->login('authentik', (object) [ + 'email' => 'new-user@example.com', + 'name' => 'New User', + 'id' => 'new-provider-id', + 'user' => ['email_verified' => false], + ], $setting))->toThrow(HttpException::class, 'Registration is disabled'); + + expect(User::count())->toBe(0); + $this->assertGuest(); +});