0, 'name' => 'Root Team', 'personal_team' => true]); $user = User::factory()->create(['id' => 0, 'email' => 'root@example.com', 'email_verified_at' => now()]); if (! $user->teams()->whereKey($team->id)->exists()) { $user->teams()->attach($team, ['role' => 'owner']); } session(['currentTeam' => $team]); test()->actingAs($user); return $user; } beforeEach(function () { $this->withoutVite(); config()->set('app.maintenance.driver', 'file'); InstanceSettings::forceCreate(['id' => 0, 'is_registration_enabled' => true]); Once::flush(); OauthSetting::create(['provider' => 'oidc']); OauthSetting::create(['provider' => 'authentik']); OauthSetting::create(['provider' => 'bitbucket']); }); it('keeps every oauth provider on one page', function () { actingAsInstanceAdmin(); $this->withoutMiddleware(DecideWhatToDoWithUser::class) ->get(route('settings.oauth')) ->assertSuccessful() ->assertSee('Authentication') ->assertSee('Registration') ->assertSee('Authentik') ->assertSee('Bitbucket') ->assertSee('OpenID Connect') ->assertSee('Disable password registration when OAuth is enabled') ->assertSee('Client secret') ->assertDontSee(route('settings.oauth.provider', 'authentik'), false); }); it('lists openid connect before the other oauth providers', function () { actingAsInstanceAdmin(); $providers = array_keys(Livewire::test(SettingsOauth::class)->get('oauth_settings_map')); expect($providers[0])->toBe('oidc'); }); it('has an icon for openid connect', function () { expect(public_path('svgs/oidc.svg'))->toBeFile(); }); it('auto saves registration policy', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class) ->set('disable_registration_when_oauth_enabled', true) ->call('saveRegistrationPolicy') ->assertHasNoErrors() ->assertDispatched('success'); expect(instanceSettings()->fresh()->disable_registration_when_oauth_enabled)->toBeTrue(); }); it('shows oidc fields with a naked okta issuer url example', function () { actingAsInstanceAdmin(); $this->withoutMiddleware(DecideWhatToDoWithUser::class) ->get(route('settings.oauth')) ->assertSuccessful() ->assertSee('OpenID Connect') ->assertSee('https://example.okta.com', false) ->assertDontSee('/oauth2/default', false); }); it('shows oidc option checkboxes', function () { actingAsInstanceAdmin(); $this->withoutMiddleware(DecideWhatToDoWithUser::class) ->get(route('settings.oauth')) ->assertSuccessful() ->assertSee('Allow OIDC user creation') ->assertSee('Require verified email') ->assertSee('Use PKCE'); }); it('does not show unknown oauth providers', function () { actingAsInstanceAdmin(); $this->withoutMiddleware(DecideWhatToDoWithUser::class) ->get('/settings/oauth/unknown') ->assertNotFound(); }); it('defaults oidc user creation and verified email requirement to enabled', function () { $setting = OauthSetting::where('provider', 'oidc')->first(); expect($setting->allow_registration)->toBeTrue() ->and($setting->require_email_verified)->toBeTrue() ->and($setting->auto_join_root_team)->toBeFalse(); }); it('persists oidc oauth settings from livewire', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class) ->set('oauth_settings_map.oidc.enabled', true) ->set('oauth_settings_map.oidc.client_id', 'client-id') ->set('oauth_settings_map.oidc.client_secret', 'secret') ->set('oauth_settings_map.oidc.redirect_uri', 'https://coolify.example.com/auth/oidc/callback') ->set('oauth_settings_map.oidc.base_url', 'https://idp.example.com') ->set('oauth_settings_map.oidc.scopes', 'openid email profile groups') ->set('oauth_settings_map.oidc.custom_label', 'Login with Okta') ->set('oauth_settings_map.oidc.allow_registration', true) ->set('oauth_settings_map.oidc.auto_join_root_team', true) ->set('oauth_settings_map.oidc.require_email_verified', true) ->set('disable_registration_when_oauth_enabled', true) ->call('submit') ->assertHasNoErrors(); $setting = OauthSetting::where('provider', 'oidc')->first(); expect($setting->enabled)->toBeTrue() ->and($setting->redirect_uri)->toBe('https://coolify.example.com/auth/oidc/callback') ->and($setting->base_url)->toBe('https://idp.example.com') ->and($setting->custom_label)->toBe('Login with Okta') ->and($setting->scopeList())->toBe(['openid', 'email', 'profile', 'groups']) ->and($setting->allow_registration)->toBeTrue() ->and($setting->auto_join_root_team)->toBeTrue(); expect(instanceSettings()->fresh()->disable_registration_when_oauth_enabled)->toBeTrue(); }); it('saves only the selected provider from provider pages', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->set('oauth_settings_map.oidc.redirect_uri', 'not-a-url') ->set('oauth_settings_map.authentik.enabled', true) ->set('oauth_settings_map.authentik.client_id', 'authentik-client') ->set('oauth_settings_map.authentik.client_secret', 'authentik-secret') ->set('oauth_settings_map.authentik.base_url', 'https://authentik.example.com') ->call('submit') ->assertHasNoErrors(); $setting = OauthSetting::where('provider', 'authentik')->first(); expect($setting->enabled)->toBeTrue() ->and($setting->client_id)->toBe('authentik-client') ->and($setting->base_url)->toBe('https://authentik.example.com'); }); it('validates oidc url fields before saving', function (string $field, string $value) { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class) ->set('oauth_settings_map.oidc.client_id', 'client-id') ->set('oauth_settings_map.oidc.client_secret', 'secret') ->set('oauth_settings_map.oidc.base_url', 'https://idp.example.com') ->set("oauth_settings_map.oidc.$field", $value) ->call('submit') ->assertHasErrors(["oauth_settings_map.oidc.$field" => 'url']); $setting = OauthSetting::where('provider', 'oidc')->first(); expect($setting->{$field})->toBeNull(); })->with([ 'invalid redirect uri' => ['redirect_uri', 'not-a-url'], 'non-http redirect uri' => ['redirect_uri', 'javascript:alert(1)'], 'invalid issuer url' => ['base_url', 'not-a-url'], 'non-http issuer url' => ['base_url', 'ftp://idp.example.com'], ]); it('rejects a redirect uri that does not point to the provider callback route', function (string $redirectUri) { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->set('oauth_settings_map.authentik.redirect_uri', $redirectUri) ->call('submit') ->assertHasErrors(['oauth_settings_map.authentik.redirect_uri']); expect(OauthSetting::where('provider', 'authentik')->first()->redirect_uri)->toBeNull(); })->with([ 'swapped segments' => ['https://coolify.example.com/auth/callback/authentik'], 'other provider' => ['https://coolify.example.com/auth/github/callback'], 'no path' => ['https://coolify.example.com'], ]); it('accepts a redirect uri on the provider callback route', function (string $redirectUri) { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->set('oauth_settings_map.authentik.redirect_uri', $redirectUri) ->call('submit') ->assertHasNoErrors(); expect(OauthSetting::where('provider', 'authentik')->first()->redirect_uri)->toBe($redirectUri); })->with([ 'callback route' => ['https://coolify.example.com/auth/authentik/callback'], 'trailing slash' => ['https://coolify.example.com/auth/authentik/callback/'], ]); it('does not enable oidc without required fields', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class) ->set('oauth_settings_map.oidc.enabled', true) ->call('instantSave', 'oidc') ->assertDispatched('error'); expect(OauthSetting::where('provider', 'oidc')->first()->enabled)->toBeFalse(); }); it('keeps provider disabled in the ui when enable validation fails', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->call('toggleProvider', 'authentik') ->assertDispatched('error') ->assertSet('oauth_settings_map.authentik.enabled', false); expect(OauthSetting::where('provider', 'authentik')->first()->enabled)->toBeFalse(); }); it('disables an enabled provider gracefully when required fields become incomplete', function () { actingAsInstanceAdmin(); OauthSetting::where('provider', 'authentik')->first()->forceFill([ 'enabled' => true, 'client_id' => 'authentik-client', 'client_secret' => 'authentik-secret', 'base_url' => 'https://authentik.example.com', ])->save(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->set('oauth_settings_map.authentik.client_secret', '') ->call('submit') ->assertDispatched('error') ->assertSet('oauth_settings_map.authentik.enabled', false); expect(OauthSetting::where('provider', 'authentik')->first()->enabled)->toBeFalse(); }); it('toggles provider enabled state from the action button', function () { actingAsInstanceAdmin(); Livewire::test(SettingsOauth::class, ['provider' => 'authentik']) ->set('oauth_settings_map.authentik.client_id', 'authentik-client') ->set('oauth_settings_map.authentik.client_secret', 'authentik-secret') ->set('oauth_settings_map.authentik.base_url', 'https://authentik.example.com') ->call('toggleProvider', 'authentik') ->assertHasNoErrors(); expect(OauthSetting::where('provider', 'authentik')->first()->enabled)->toBeTrue(); }); it('stores instance authentication audit events without the admin current team', function () { $this->withoutDefer(); $otherTeam = Team::factory()->create(); $instanceAdmin = actingAsInstanceAdmin(); $instanceAdmin->teams()->attach($otherTeam, ['role' => 'owner']); $instanceAdmin->unsetRelation('teams'); session(['currentTeam' => $otherTeam]); Livewire::test(SettingsOauth::class) ->set('disable_registration_when_oauth_enabled', true) ->call('saveRegistrationPolicy') ->assertHasNoErrors(); $event = AuditEvent::query()->where('event', 'ui.instance.authentication.updated')->sole(); expect($event->team_id)->toBeNull(); $otherTeamAdmin = User::factory()->create(); $otherTeam->members()->attach($otherTeamAdmin, ['role' => 'admin']); $this->actingAs($otherTeamAdmin); session(['currentTeam' => $otherTeam]); Livewire::test(AuditLog::class) ->assertDontSee($instanceAdmin->email) ->assertDontSee($event->description); });