feat: add routes, lang, tests, stubs, docs, and docker configurations
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Services\SystemConfig\SystemConfigService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laravel\Socialite\Contracts\User as SocialiteUserContract;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
beforeEach(function () {
|
||||
$ref = new ReflectionClass(SystemConfigService::class);
|
||||
$prop = $ref->getProperty('resolvedSettings');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue(null, null);
|
||||
Cache::flush();
|
||||
Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']);
|
||||
});
|
||||
|
||||
function enableOauth(string $provider): void
|
||||
{
|
||||
app(SystemConfigService::class)->update(['feature_'.$provider.'_oauth' => true]);
|
||||
}
|
||||
|
||||
function fakeSocialiteUser(array $overrides = []): SocialiteUserContract
|
||||
{
|
||||
$u = new Laravel\Socialite\Two\User;
|
||||
$u->id = $overrides['id'] ?? 'oauth-id-123';
|
||||
$u->name = $overrides['name'] ?? 'OAuth User';
|
||||
$u->email = $overrides['email'] ?? 'oauth@example.com';
|
||||
$u->avatar = $overrides['avatar'] ?? 'https://example.com/avatar.png';
|
||||
$u->user = $overrides['user'] ?? [];
|
||||
|
||||
return $u;
|
||||
}
|
||||
|
||||
test('redirect returns 404 when provider feature is disabled', function () {
|
||||
$this->get('/auth/google')->assertNotFound();
|
||||
});
|
||||
|
||||
test('redirect issues a redirect when provider feature is enabled', function () {
|
||||
enableOauth('google');
|
||||
|
||||
Socialite::shouldReceive('driver->redirect')
|
||||
->andReturn(redirect('https://accounts.google.com/o/oauth2/auth?fake=1'));
|
||||
|
||||
$this->get('/auth/google')->assertRedirect();
|
||||
expect(session('social_auth_provider'))->toBe('google');
|
||||
});
|
||||
|
||||
test('callback without provider session redirects to login with error', function () {
|
||||
$this->get('/auth/callback')
|
||||
->assertRedirect('/login')
|
||||
->assertSessionHas('error');
|
||||
});
|
||||
|
||||
test('callback rejects unverified email from provider', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
||||
'user' => ['email_verified' => false],
|
||||
]));
|
||||
|
||||
$this->get('/auth/callback')
|
||||
->assertRedirect('/login')
|
||||
->assertSessionHas('error');
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
|
||||
test('callback creates a new user via provider id and assigns user role', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
||||
'id' => 'google-uid-9',
|
||||
'email' => 'fresh@example.com',
|
||||
'name' => 'Fresh User',
|
||||
]));
|
||||
|
||||
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
||||
|
||||
$user = User::where('email', 'fresh@example.com')->first();
|
||||
expect($user)->not->toBeNull();
|
||||
expect($user->google_id)->toBe('google-uid-9');
|
||||
expect($user->hasRole('User'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('callback links to existing user with matching email when no provider id yet', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
$existing = User::factory()->create([
|
||||
'email' => 'link@example.com',
|
||||
'google_id' => null,
|
||||
]);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
||||
'id' => 'google-uid-link',
|
||||
'email' => 'link@example.com',
|
||||
]));
|
||||
|
||||
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
||||
|
||||
expect($existing->fresh()->google_id)->toBe('google-uid-link');
|
||||
$this->assertAuthenticatedAs($existing->fresh());
|
||||
});
|
||||
|
||||
test('callback refuses to overwrite a different existing oauth identity', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
$existing = User::factory()->create([
|
||||
'email' => 'taken@example.com',
|
||||
'google_id' => 'different-google-id',
|
||||
]);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
||||
'id' => 'attacker-id',
|
||||
'email' => 'taken@example.com',
|
||||
]));
|
||||
|
||||
$this->get('/auth/callback')
|
||||
->assertRedirect('/login')
|
||||
->assertSessionHas('error');
|
||||
|
||||
expect($existing->fresh()->google_id)->toBe('different-google-id');
|
||||
$this->assertGuest();
|
||||
});
|
||||
|
||||
test('callback re-uses user matched by provider id', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
$existing = User::factory()->create(['google_id' => 'stable-id']);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn(fakeSocialiteUser([
|
||||
'id' => 'stable-id',
|
||||
'email' => $existing->email,
|
||||
]));
|
||||
|
||||
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
||||
$this->assertAuthenticatedAs($existing->fresh());
|
||||
});
|
||||
|
||||
test('callback on socialite exception redirects to login with error', function () {
|
||||
enableOauth('google');
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andThrow(new Exception('OAuth boom'));
|
||||
|
||||
$this->get('/auth/callback')
|
||||
->assertRedirect('/login')
|
||||
->assertSessionHas('error');
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
Reference in New Issue
Block a user