diff --git a/server/SHServ/Controllers/AuthController.php b/server/SHServ/Controllers/AuthController.php index 5a7a738..697def6 100644 --- a/server/SHServ/Controllers/AuthController.php +++ b/server/SHServ/Controllers/AuthController.php @@ -4,24 +4,34 @@ namespace SHServ\Controllers; +use GNexus\GAuth\DTO\TokenSet; use SHServ\Integrations\GAuth\AuthControllerTrait; use SHServ\Integrations\GAuth\AuthService; use SHServ\Integrations\GAuth\PermissionResolver; +use SHServ\Integrations\GAuth\Store\DbTokenStore; use SHServ\Integrations\GAuth\UserResolver; class AuthController extends \SHServ\Middleware\Controller { use AuthControllerTrait; + private function logAuth(string $msg): void + { + $path = dirname(__DIR__, 2) . '/Logs/gauth_debug.log'; + @file_put_contents($path, date('Y-m-d H:i:s') . ' ' . $msg . "\n", FILE_APPEND | LOCK_EX); + } + /** * GET /auth/login * Redirect user to gnexus-auth authorization page. */ public function login() { + $this->logAuth('login() called'); $service = new AuthService(); $returnTo = $_GET['return_to'] ?? '/'; $url = $service->buildLoginUrl($returnTo); + $this->logAuth("login() redirect to: {$url}"); return $this->utils()->redirect($url); } @@ -31,29 +41,77 @@ */ public function callback() { - $code = isset($_GET['code']) ? (string) $_GET['code'] : ''; - $state = isset($_GET['state']) ? (string) $_GET['state'] : ''; - - if ($code === '' || $state === '') { - return $this->utils()->response_error('invalid_callback'); + if (session_status() === PHP_SESSION_NONE) { + @session_start(); } - $service = new AuthService(); - + $this->logAuth('callback() called'); try { - $user = $service->handleCallback($code, $state); - } catch (\GNexus\GAuth\Exception\GAuthException $e) { - return $this->utils()->response_error('auth_failed', [], ['message' => $e->getMessage()]); + $code = isset($_GET['code']) ? (string) $_GET['code'] : ''; + $state = isset($_GET['state']) ? (string) $_GET['state'] : ''; + + $this->logAuth("callback() code={$code}, state={$state}"); + + if ($code === '' || $state === '') { + $this->logAuth('callback() empty code/state'); + return $this->utils()->response_error('invalid_callback'); + } + + $service = new AuthService(); + $this->logAuth('callback() AuthService created'); + + try { + $user = $service->handleCallback($code, $state); + $this->logAuth('callback() handleCallback OK, userId=' . $user->userId); + } catch (\GNexus\GAuth\Exception\GAuthException $e) { + $this->logAuth('callback() GAuthException: ' . $e->getMessage()); + return $this->utils()->response_error('auth_failed', [], ['message' => $e->getMessage()]); + } catch (\Throwable $e) { + $this->logAuth('callback() Throwable in handleCallback: ' . get_class($e) . ' - ' . $e->getMessage()); + throw $e; + } + + $resolver = new UserResolver(); + $this->logAuth('callback() UserResolver created'); + + try { + $localUserId = $resolver->resolve($user); + $this->logAuth('callback() resolve OK, localUserId=' . $localUserId); + } catch (\Throwable $e) { + $this->logAuth('callback() Throwable in resolve: ' . get_class($e) . ' - ' . $e->getMessage()); + throw $e; + } + + $_SESSION['shserv_user_id'] = $localUserId; + + // Persist tokens to DB now that we have the local user ID + $sessionToken = $_SESSION['shserv_auth_token'] ?? null; + $tokenData = $_SESSION['shserv_last_token_set'] ?? null; + if ($sessionToken && $tokenData) { + $expiresAt = $tokenData['expires_at'] ? new \DateTimeImmutable($tokenData['expires_at']) : null; + $tokenSet = new TokenSet( + accessToken: $tokenData['access_token'], + refreshToken: $tokenData['refresh_token'] ?? null, + tokenType: $tokenData['token_type'] ?? 'Bearer', + expiresIn: $expiresAt ? (int) $expiresAt->format('U') - time() : 0, + expiresAt: $expiresAt, + ); + $dbStore = new DbTokenStore(app()->thin_builder); + $dbStore->put($sessionToken, $tokenSet, $localUserId); + unset($_SESSION['shserv_last_token_set']); + $this->logAuth('callback() DbTokenStore->put() OK'); + } + + // Redirect back to app + $context = $_SESSION['gauth_state'][$state]['context'] ?? []; + $returnTo = $context['return_to'] ?? '/'; + $this->logAuth("callback() redirect to: {$returnTo}"); + return $this->utils()->redirect($returnTo); + } catch (\Throwable $e) { + $this->logAuth('callback() UNCAUGHT Throwable: ' . get_class($e) . ' - ' . $e->getMessage()); + $this->logAuth('callback() Stack: ' . str_replace("\n", ' | ', $e->getTraceAsString())); + throw $e; } - - $resolver = new UserResolver(); - $localUserId = $resolver->resolve($user); - $_SESSION['shserv_user_id'] = $localUserId; - - // Redirect back to app - $context = $_SESSION['gauth_state'][$state]['context'] ?? []; - $returnTo = $context['return_to'] ?? '/'; - return $this->utils()->redirect($returnTo); } /** diff --git a/server/SHServ/Integrations/GAuth/AuthControllerTrait.php b/server/SHServ/Integrations/GAuth/AuthControllerTrait.php index 1756ce9..298a187 100644 --- a/server/SHServ/Integrations/GAuth/AuthControllerTrait.php +++ b/server/SHServ/Integrations/GAuth/AuthControllerTrait.php @@ -48,6 +48,10 @@ */ protected function resolve_user(): ?array { + if (session_status() === PHP_SESSION_NONE) { + @session_start(); + } + // 1. Session-based auth $sessionUserId = $_SESSION['shserv_user_id'] ?? null; if ($sessionUserId) { diff --git a/server/SHServ/Integrations/GAuth/AuthService.php b/server/SHServ/Integrations/GAuth/AuthService.php index 5bda4b0..ca7eb05 100644 --- a/server/SHServ/Integrations/GAuth/AuthService.php +++ b/server/SHServ/Integrations/GAuth/AuthService.php @@ -79,14 +79,20 @@ $tokenSet = $this->client->exchangeAuthorizationCode($code, $state); $user = $this->client->fetchUser($tokenSet->accessToken); - // Persist tokens + // Persist tokens in session (DB persistence happens after user resolution) $sessionToken = bin2hex(random_bytes(32)); $_SESSION['shserv_auth_token'] = $sessionToken; $_SESSION['shserv_access_token'] = $tokenSet->accessToken; $_SESSION['shserv_user_id'] = $user->userId; - $dbStore = new DbTokenStore(app()->thin_builder); - $dbStore->put($sessionToken, $tokenSet); + // Store token set temporarily for callback controller to persist to DB + $_SESSION['shserv_last_token_set'] = [ + 'access_token' => $tokenSet->accessToken, + 'refresh_token' => $tokenSet->refreshToken, + 'token_type' => $tokenSet->tokenType, + 'expires_in' => $tokenSet->expiresIn, + 'expires_at' => $tokenSet->expiresAt ? $tokenSet->expiresAt->format('Y-m-d H:i:s') : null, + ]; return $user; } diff --git a/server/SHServ/Integrations/GAuth/Store/DbTokenStore.php b/server/SHServ/Integrations/GAuth/Store/DbTokenStore.php index 85de1d5..9e97083 100644 --- a/server/SHServ/Integrations/GAuth/Store/DbTokenStore.php +++ b/server/SHServ/Integrations/GAuth/Store/DbTokenStore.php @@ -17,7 +17,7 @@ $this->tb = $tb; } - public function put(string $key, TokenSet $tokenSet): void + public function put(string $key, TokenSet $tokenSet, ?int $userId = null): void { $expiresAt = $tokenSet->expiresAt; $data = [ @@ -28,6 +28,10 @@ 'updated_at' => date('Y-m-d H:i:s'), ]; + if ($userId !== null) { + $data['user_id'] = $userId; + } + $existing = $this->tb->select('shserv_sessions', ['id'], [['session_token', '=', $key]]); if ($existing) { $this->tb->update('shserv_sessions', $data, [['session_token', '=', $key]]); @@ -60,4 +64,12 @@ { $this->tb->delete('shserv_sessions', [['session_token', '=', $key]]); } + + public function updateUserId(string $key, int $userId): void + { + $this->tb->update('shserv_sessions', [ + 'user_id' => $userId, + 'updated_at' => date('Y-m-d H:i:s'), + ], [['session_token', '=', $key]]); + } } diff --git a/server/SHServ/Integrations/GAuth/UserResolver.php b/server/SHServ/Integrations/GAuth/UserResolver.php index eb11369..52c736d 100644 --- a/server/SHServ/Integrations/GAuth/UserResolver.php +++ b/server/SHServ/Integrations/GAuth/UserResolver.php @@ -25,7 +25,7 @@ $systemRole = $user->systemRole ?? 'user'; $status = $user->status ?? 'active'; $displayName = $user->profile['display_name'] ?? ($user->profile['username'] ?? $user->email); - $avatarUrl = $user->avatarUrl(); + $avatarUrl = $user->profile['avatar_url'] ?? ($user->profile['avatar'] ?? ''); $data = [ 'email' => $user->email, @@ -49,7 +49,7 @@ $data['created_at'] = date('Y-m-d H:i:s'); $tb->insert('shserv_users', $data); - $userId = (int) $tb->getLastInsertedId(); + $userId = (int) $tb->lastInsertId(); $this->syncPermissions($userId, $clientAccess); diff --git a/webclient/src/api/http.js b/webclient/src/api/http.js index c01dda4..c0f4e7e 100644 --- a/webclient/src/api/http.js +++ b/webclient/src/api/http.js @@ -59,6 +59,7 @@ method, headers, signal: controller.signal, + credentials: "include", }; if (options.signal) {