feat(auth): improve OAuth buttons and OIDC settings layout

Add provider icons and full-width centered login buttons, organize OIDC fields, and remove hardcoded user IDs from the seeder.
This commit is contained in:
Andras Bacsai
2026-08-18 14:33:21 +02:00
parent 8400e26a3f
commit ce2ea0544e
6 changed files with 97 additions and 25 deletions
-2
View File
@@ -15,12 +15,10 @@ class UserSeeder extends Seeder
'email' => 'test@example.com',
]);
User::factory()->create([
'id' => 1,
'name' => 'Normal User (but in root team)',
'email' => 'test2@example.com',
]);
User::factory()->create([
'id' => 2,
'name' => 'Normal User (not in root team)',
'email' => 'test3@example.com',
]);
+5 -1
View File
@@ -80,10 +80,14 @@
@if ($enabled_oauth_providers->isNotEmpty())
<div class="auth-divider"><span>Or continue with</span></div>
<div class="grid gap-2 sm:grid-cols-2">
<div class="flex flex-col gap-2">
@foreach ($enabled_oauth_providers as $provider_setting)
<x-forms.button class="w-full justify-center" type="button"
onclick="document.location.href='/auth/{{ $provider_setting->provider }}/redirect'">
@if ($provider_setting->provider !== 'oidc')
<img class="size-5 shrink-0 dark:invert"
src="{{ asset('svgs/'.$provider_setting->provider.'.svg') }}" alt="" aria-hidden="true">
@endif
{{ $provider_setting->loginLabel() }}
</x-forms.button>
@endforeach
@@ -55,14 +55,39 @@
</x-slot:actions>
<div class="grid gap-4 lg:grid-cols-2">
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.redirect_uri"
placeholder="{{ route('auth.callback', $provider) }}" label="Redirect URI" />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_id" label="Client ID" required />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_secret" type="password"
label="Client secret" autocomplete="new-password" required />
@if ($provider === 'oidc')
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.redirect_uri"
placeholder="{{ route('auth.callback', $provider) }}" label="Redirect URI" />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.base_url" label="Issuer URL" required
helper="OpenID Provider issuer URL, for example https://example.okta.com. Coolify uses it to discover the authorization, token, userinfo, and JWKS endpoints." />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_id" label="Client ID" required />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_secret" type="password"
label="Client secret" autocomplete="new-password" required />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.scopes" label="Scopes"
helper="Must include openid. Common scopes are openid email profile groups." />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.clock_skew_seconds" type="number"
label="Clock skew (seconds)" />
<div class="lg:col-span-2">
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.custom_label" label="Login button label"
placeholder="Login with SSO" />
</div>
@else
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.redirect_uri"
placeholder="{{ route('auth.callback', $provider) }}" label="Redirect URI" />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_id" label="Client ID" required />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.client_secret" type="password"
label="Client secret" autocomplete="new-password" required />
@endif
@if ($provider === 'azure')
<x-forms.input canGate="update" :canResource="$settings"
@@ -82,20 +107,6 @@
:required="in_array($provider, ['authentik', 'clerk'], true)" />
@endif
@if ($provider === 'oidc')
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.base_url" label="Issuer URL" required
helper="OpenID Provider issuer URL, for example https://example.okta.com. Coolify uses it to discover the authorization, token, userinfo, and JWKS endpoints." />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.custom_label" label="Login button label"
placeholder="Login with SSO" />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.scopes" label="Scopes"
helper="Must include openid. Common scopes are openid email profile groups." />
<x-forms.input canGate="update" :canResource="$settings"
id="oauth_settings_map.{{ $provider }}.clock_skew_seconds" type="number"
label="Clock skew (seconds)" />
@endif
</div>
<div class="mt-4 grid gap-3 lg:grid-cols-2">
+22
View File
@@ -37,6 +37,28 @@ test('auth pages use the Coollabs purple background glow', function () {
->not->toMatch('/\.auth-shell\s*\{[^}]*color-mix\(in oklab, var\(--color-accent\) 9%, transparent\)/s');
});
test('external login providers are centered and full width', function () {
$login = file_get_contents(resource_path('views/auth/login.blade.php'));
expect($login)
->toContain('class="flex flex-col gap-2"')
->toContain('class="w-full justify-center"')
->not->toContain('sm:w-[calc(50%-0.25rem)]');
});
test('external login providers display their icons except oidc', function () {
$login = file_get_contents(resource_path('views/auth/login.blade.php'));
expect($login)
->toContain("@if (\$provider_setting->provider !== 'oidc')")
->toContain("asset('svgs/'.\$provider_setting->provider.'.svg')")
->toContain('class="size-5 shrink-0 dark:invert"');
foreach (['authentik', 'azure', 'bitbucket', 'clerk', 'discord', 'github', 'gitlab', 'google', 'infomaniak', 'zitadel'] as $provider) {
expect(public_path("svgs/{$provider}.svg"))->toBeFile();
}
});
test('error pages use the Coollabs purple background glow', function () {
$styles = file_get_contents(resource_path('css/app.css'));
+21
View File
@@ -94,6 +94,27 @@ it('shows oidc fields with a naked okta issuer url example', function () {
->assertDontSee('/oauth2/default', false);
});
it('groups oidc fields in the expected desktop order', function () {
$view = file_get_contents(resource_path('views/livewire/settings-oauth.blade.php'));
$fields = [
'redirect_uri',
'base_url',
'client_id',
'client_secret',
'scopes',
'clock_skew_seconds',
'custom_label',
];
$positions = array_map(
fn (string $field): int|false => strpos($view, "id=\"oauth_settings_map.{{ \$provider }}.$field\""),
$fields,
);
expect($positions)->not->toContain(false)
->and($positions)->toBe(collect($positions)->sort()->values()->all())
->and($view)->toContain('<div class="lg:col-span-2">');
});
it('shows provider enable controls as settings section actions', function () {
actingAsInstanceAdmin();
+16
View File
@@ -0,0 +1,16 @@
<?php
use App\Models\User;
use Database\Seeders\UserSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('leaves the user id sequence ready for new development users', function () {
$this->seed(UserSeeder::class);
$user = User::factory()->create();
expect(User::query()->orderBy('id')->pluck('id')->all())->toBe([0, 1, 2, 3])
->and($user->id)->toBe(3);
});