Merge pull request #11 from trypost-it/feature/google-auth-toggle

feat: Google Auth toggle — TRYPOST_GOOGLE_AUTH_ENABLED
This commit is contained in:
Paulo Castellano 2026-04-14 14:50:48 -03:00 committed by GitHub
commit 450635f53e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 177 additions and 80 deletions

View file

@ -47,6 +47,7 @@ public function share(Request $request): array
'name' => $name,
])->values()->all(),
'selfHosted' => config('trypost.self_hosted'),
'googleAuthEnabled' => config('trypost.google_auth_enabled'),
];
}
}

View file

@ -16,6 +16,18 @@
'self_hosted' => env('SELF_HOSTED', true),
/*
|--------------------------------------------------------------------------
| Google Authentication
|--------------------------------------------------------------------------
|
| Enable or disable "Login with Google" on the login and register pages.
| Disable this if you don't have Google OAuth credentials configured.
|
*/
'google_auth_enabled' => env('GOOGLE_AUTH_ENABLED', false),
/*
|--------------------------------------------------------------------------
| Social Platforms
@ -29,40 +41,40 @@
'platforms' => [
'linkedin' => [
'enabled' => env('TRYPOST_LINKEDIN_ENABLED', true),
'enabled' => env('LINKEDIN_ENABLED', true),
],
'linkedin-page' => [
'enabled' => env('TRYPOST_LINKEDIN_PAGE_ENABLED', true),
'enabled' => env('LINKEDIN_PAGE_ENABLED', true),
],
'x' => [
'enabled' => env('TRYPOST_X_ENABLED', true),
'enabled' => env('X_ENABLED', true),
],
'tiktok' => [
'enabled' => env('TRYPOST_TIKTOK_ENABLED', true),
'enabled' => env('TIKTOK_ENABLED', true),
],
'youtube' => [
'enabled' => env('TRYPOST_YOUTUBE_ENABLED', true),
'enabled' => env('YOUTUBE_ENABLED', true),
],
'facebook' => [
'enabled' => env('TRYPOST_FACEBOOK_ENABLED', true),
'enabled' => env('FACEBOOK_ENABLED', true),
],
'instagram' => [
'enabled' => env('TRYPOST_INSTAGRAM_ENABLED', true),
'enabled' => env('INSTAGRAM_ENABLED', true),
],
'instagram-facebook' => [
'enabled' => env('TRYPOST_INSTAGRAM_FACEBOOK_ENABLED', true),
'enabled' => env('INSTAGRAM_FACEBOOK_ENABLED', true),
],
'threads' => [
'enabled' => env('TRYPOST_THREADS_ENABLED', true),
'enabled' => env('THREADS_ENABLED', true),
],
'pinterest' => [
'enabled' => env('TRYPOST_PINTEREST_ENABLED', true),
'enabled' => env('PINTEREST_ENABLED', true),
],
'bluesky' => [
'enabled' => env('TRYPOST_BLUESKY_ENABLED', true),
'enabled' => env('BLUESKY_ENABLED', true),
],
'mastodon' => [
'enabled' => env('TRYPOST_MASTODON_ENABLED', true),
'enabled' => env('MASTODON_ENABLED', true),
],
],

View file

@ -66,16 +66,18 @@ defineProps<{
</Button>
</div>
<div
class="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">{{ $t('auth.or_continue_with') }}</span>
</div>
<template v-if="$page.props.googleAuthEnabled">
<div
class="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">{{ $t('auth.or_continue_with') }}</span>
</div>
<Button variant="outline" class="w-full" as="a" :href="googleRedirect.url()">
<img src="/images/social/google.svg" alt="Google" class="size-4" />
{{ $t('auth.google_login') }}
</Button>
<Button variant="outline" class="w-full" as="a" :href="googleRedirect.url()">
<img src="/images/social/google.svg" alt="Google" class="size-4" />
{{ $t('auth.google_login') }}
</Button>
</template>
<div class="text-center text-sm text-muted-foreground">
{{ $t('auth.login.no_account') }}

View file

@ -123,16 +123,18 @@ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
</Button>
</div>
<div
class="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">{{ $t('auth.or_continue_with') }}</span>
</div>
<template v-if="$page.props.googleAuthEnabled">
<div
class="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border"
>
<span class="relative z-10 bg-background px-2 text-muted-foreground">{{ $t('auth.or_continue_with') }}</span>
</div>
<Button variant="outline" class="w-full" as="a" :href="googleRedirect.url()">
<img src="/images/social/google.svg" alt="Google" class="size-4" />
{{ $t('auth.google_signup') }}
</Button>
<Button variant="outline" class="w-full" as="a" :href="googleRedirect.url()">
<img src="/images/social/google.svg" alt="Google" class="size-4" />
{{ $t('auth.google_signup') }}
</Button>
</template>
<div class="text-center text-sm text-muted-foreground">
{{ $t('auth.register.has_account') }}

View file

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
test('login page loads when google auth is disabled', function () {
config(['trypost.google_auth_enabled' => false]);
$response = $this->get(route('login'));
$response->assertOk();
});
test('login page loads when google auth is enabled', function () {
config(['trypost.google_auth_enabled' => true]);
$response = $this->get(route('login'));
$response->assertOk();
});
test('register page loads when google auth is disabled', function () {
config(['trypost.google_auth_enabled' => false]);
$response = $this->get(route('register'));
$response->assertOk();
});
test('register page loads when google auth is enabled', function () {
config(['trypost.google_auth_enabled' => true]);
$response = $this->get(route('register'));
$response->assertOk();
});
test('login page shares google auth enabled prop as false when disabled', function () {
config(['trypost.google_auth_enabled' => false]);
$response = $this->get(route('login'));
$response->assertOk();
$page = $response->original->getData()['page'];
expect($page['props']['googleAuthEnabled'])->toBeFalse();
});
test('login page shares google auth enabled prop as true when enabled', function () {
config(['trypost.google_auth_enabled' => true]);
$response = $this->get(route('login'));
$response->assertOk();
$page = $response->original->getData()['page'];
expect($page['props']['googleAuthEnabled'])->toBeTrue();
});
test('google auth redirect route exists', function () {
$response = $this->get(route('auth.google.redirect'));
// Should redirect to Google OAuth, not 404
$response->assertRedirect();
});
test('google auth callback route exists', function () {
$response = $this->get(route('auth.google.callback'));
// Should redirect to login on failure (no OAuth code), not 404
$response->assertRedirect(route('login'));
});

View file

@ -20,14 +20,16 @@
});
test('facebook connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('usingGraphVersion')->andReturnSelf();
$driverMock->shouldReceive('setScopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.facebook.com/v25.0/dialog/oauth?test=1',
]));
Socialite::shouldReceive('driver')
->with('facebook')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.facebook.com/v21.0/dialog/oauth?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -19,14 +19,15 @@
});
test('instagram connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.instagram.com/oauth/authorize?test=1',
]));
Socialite::shouldReceive('driver')
->with('instagram')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.instagram.com/oauth/authorize?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -20,14 +20,15 @@
});
test('linkedin connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.linkedin.com/oauth/v2/authorization?test=1',
]));
Socialite::shouldReceive('driver')
->with('linkedin')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.linkedin.com/oauth/v2/authorization?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -20,15 +20,16 @@
});
test('linkedin page connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('with')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.linkedin.com/oauth/v2/authorization?test=1',
]));
Socialite::shouldReceive('driver')
->with('linkedin-openid')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'with' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.linkedin.com/oauth/v2/authorization?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -19,14 +19,15 @@
});
test('pinterest connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.pinterest.com/oauth?test=1',
]));
Socialite::shouldReceive('driver')
->with('pinterest')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.pinterest.com/oauth?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -19,14 +19,15 @@
});
test('tiktok connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://www.tiktok.com/v2/auth/authorize?test=1',
]));
Socialite::shouldReceive('driver')
->with('tiktok')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://www.tiktok.com/v2/auth/authorize?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -19,14 +19,15 @@
});
test('x connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://twitter.com/i/oauth2/authorize?test=1',
]));
Socialite::shouldReceive('driver')
->with('x')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://twitter.com/i/oauth2/authorize?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')

View file

@ -20,15 +20,16 @@
});
test('youtube connect redirects to oauth provider', function () {
$driverMock = Mockery::mock();
$driverMock->shouldReceive('scopes')->andReturnSelf();
$driverMock->shouldReceive('with')->andReturnSelf();
$driverMock->shouldReceive('redirect')->andReturn(Mockery::mock([
'getTargetUrl' => 'https://accounts.google.com/o/oauth2/v2/auth?test=1',
]));
Socialite::shouldReceive('driver')
->with('google')
->andReturn(Mockery::mock([
'scopes' => Mockery::self(),
'with' => Mockery::self(),
'redirect' => Mockery::mock([
'getTargetUrl' => 'https://accounts.google.com/o/oauth2/v2/auth?test=1',
]),
]));
->andReturn($driverMock);
$response = $this->actingAs($this->user)
->withHeader('X-Inertia', 'true')