diff --git a/.env.example b/.env.example index cdd5e401..8907431a 100644 --- a/.env.example +++ b/.env.example @@ -84,5 +84,6 @@ CADDY_AUTHORIZED_IPS= GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT_URL=http://localhost:3000/settings/connections/callback/google +GOOGLE_AUTH_REDIRECT_URL=http://localhost:3000/oauth/google/callback -GOOGLE_FONTS_API_KEY= \ No newline at end of file +GOOGLE_FONTS_API_KEY= diff --git a/app/Http/Controllers/Auth/OAuthController.php b/app/Http/Controllers/Auth/OAuthController.php index 2c634f52..dfb1a407 100644 --- a/app/Http/Controllers/Auth/OAuthController.php +++ b/app/Http/Controllers/Auth/OAuthController.php @@ -2,12 +2,12 @@ namespace App\Http\Controllers\Auth; -use App\Exceptions\EmailTakenException; use App\Http\Controllers\Controller; +use App\Integrations\OAuth\OAuthProviderService; use App\Models\OAuthProvider; use App\Models\User; +use App\Models\Workspace; use Illuminate\Foundation\Auth\AuthenticatesUsers; -use Laravel\Socialite\Facades\Socialite; class OAuthController extends Controller { @@ -31,11 +31,11 @@ class OAuthController extends Controller * @param string $provider * @return \Illuminate\Http\RedirectResponse */ - public function redirect($provider) + public function redirect(OAuthProviderService $provider) { - return [ - 'url' => Socialite::driver($provider)->stateless()->redirect()->getTargetUrl(), - ]; + return response()->json([ + 'url' => $provider->getDriver()->setRedirectUrl(config('services.google.auth_redirect'))->getRedirectUrl() + ]); } /** @@ -44,69 +44,103 @@ class OAuthController extends Controller * @param string $driver * @return \Illuminate\Http\Response */ - public function handleCallback($provider) + public function handleCallback(OAuthProviderService $provider) { - $user = Socialite::driver($provider)->stateless()->user(); - $user = $this->findOrCreateUser($provider, $user); + try { + $driverUser = $provider->getDriver()->setRedirectUrl(config('services.google.auth_redirect'))->getUser(); + } catch (\Exception $e) { + return $this->error([ + "message" => "OAuth service failed to authenticate: " . $e->getMessage() + ]); + } + $user = $this->findOrCreateUser($provider, $driverUser); + + if (!$user) { + return $this->error([ + "message" => "User not found." + ]); + } + + if ($user->has_registered) { + return $this->error([ + "message" => "This email is already registered. Please sign in with your password." + ]); + } $this->guard()->setToken( $token = $this->guard()->login($user) ); - return view('oauth/callback', [ + return response()->json([ 'token' => $token, 'token_type' => 'bearer', 'expires_in' => $this->guard()->getPayload()->get('exp') - time(), + 'new_user' => $user->new_user ]); } /** - * @param string $provider - * @param \Laravel\Socialite\Contracts\User $sUser - * @return \App\Models\User + * @p aram \Laravel\Socialite\Contracts\User $socialiteUser + * @return \App\Models\User | null */ - protected function findOrCreateUser($provider, $user) + protected function findOrCreateUser($provider, $socialiteUser) { $oauthProvider = OAuthProvider::where('provider', $provider) - ->where('provider_user_id', $user->getId()) + ->where('provider_user_id', $socialiteUser->getId()) ->first(); if ($oauthProvider) { $oauthProvider->update([ - 'access_token' => $user->token, - 'refresh_token' => $user->refreshToken, + 'access_token' => $socialiteUser->token, + 'refresh_token' => $socialiteUser->refreshToken, ]); return $oauthProvider->user; } - if (User::where('email', $user->getEmail())->exists()) { - throw new EmailTakenException(); + + if (!$provider->getDriver()->canCreateUser()) { + return null; } - return $this->createUser($provider, $user); - } + $email = strtolower($socialiteUser->getEmail()); + $user = User::whereEmail($email)->first(); + + if ($user) { + $user->has_registered = true; + return $user; + } - /** - * @param string $provider - * @param \Laravel\Socialite\Contracts\User $sUser - * @return \App\Models\User - */ - protected function createUser($provider, $sUser) - { $user = User::create([ - 'name' => $sUser->getName(), - 'email' => $sUser->getEmail(), + 'name' => $socialiteUser->getName(), + 'email' => $email, 'email_verified_at' => now(), ]); - $user->oauthProviders()->create([ - 'provider' => $provider, - 'provider_user_id' => $sUser->getId(), - 'access_token' => $sUser->token, - 'refresh_token' => $sUser->refreshToken, + // Create and sync workspace + $workspace = Workspace::create([ + 'name' => 'My Workspace', + 'icon' => '🧪', ]); + $user->workspaces()->sync([ + $workspace->id => [ + 'role' => User::ROLE_ADMIN, + ], + ], false); + $user->new_user = true; + + OAuthProvider::create( + [ + 'user_id' => $user->id, + 'provider' => $provider, + 'provider_user_id' => $socialiteUser->getId(), + 'access_token' => $socialiteUser->token, + 'refresh_token' => $socialiteUser->refreshToken, + 'name' => $socialiteUser->getName(), + 'email' => $socialiteUser->getEmail(), + ] + ); return $user; } } diff --git a/app/Http/Controllers/FontsController.php b/app/Http/Controllers/FontsController.php index cfff8532..f528b17f 100644 --- a/app/Http/Controllers/FontsController.php +++ b/app/Http/Controllers/FontsController.php @@ -10,7 +10,7 @@ class FontsController extends Controller public function index(Request $request) { return \Cache::remember('google_fonts', 60 * 60, function () { - $url = "https://www.googleapis.com/webfonts/v1/webfonts?sort=popularity&key=" . config('services.google_fonts_api_key'); + $url = "https://www.googleapis.com/webfonts/v1/webfonts?sort=popularity&key=" . config('services.google.fonts_api_key'); $response = Http::get($url); if ($response->successful()) { $fonts = collect($response->json()['items'])->filter(function ($font) { diff --git a/app/Integrations/OAuth/Drivers/Contracts/OAuthDriver.php b/app/Integrations/OAuth/Drivers/Contracts/OAuthDriver.php index 98f64e3d..78f04e29 100644 --- a/app/Integrations/OAuth/Drivers/Contracts/OAuthDriver.php +++ b/app/Integrations/OAuth/Drivers/Contracts/OAuthDriver.php @@ -7,5 +7,7 @@ use Laravel\Socialite\Contracts\User; interface OAuthDriver { public function getRedirectUrl(): string; + public function setRedirectUrl($url): self; public function getUser(): User; + public function canCreateUser(): bool; } diff --git a/app/Integrations/OAuth/Drivers/OAuthGoogleDriver.php b/app/Integrations/OAuth/Drivers/OAuthGoogleDriver.php index b1ab962a..aa73d6ad 100644 --- a/app/Integrations/OAuth/Drivers/OAuthGoogleDriver.php +++ b/app/Integrations/OAuth/Drivers/OAuthGoogleDriver.php @@ -10,6 +10,8 @@ use Laravel\Socialite\Two\GoogleProvider; class OAuthGoogleDriver implements OAuthDriver { + private ?string $redirectUrl = null; + protected GoogleProvider $provider; public function __construct() @@ -22,6 +24,7 @@ class OAuthGoogleDriver implements OAuthDriver return $this->provider ->scopes([Sheets::DRIVE_FILE]) ->stateless() + ->redirectUrl($this->redirectUrl ?? config('services.google.redirect')) ->with([ 'access_type' => 'offline', 'prompt' => 'consent select_account' @@ -34,6 +37,19 @@ class OAuthGoogleDriver implements OAuthDriver { return $this->provider ->stateless() + ->redirectUrl($this->redirectUrl ?? config('services.google.redirect')) ->user(); } + + public function canCreateUser(): bool + { + return true; + } + + public function setRedirectUrl($url): OAuthDriver + { + $this->redirectUrl = $url; + return $this; + } + } diff --git a/client/components/pages/auth/components/LoginForm.vue b/client/components/pages/auth/components/LoginForm.vue index cefda324..a0644198 100644 --- a/client/components/pages/auth/components/LoginForm.vue +++ b/client/components/pages/auth/components/LoginForm.vue @@ -30,7 +30,7 @@ /> -