<?php
declare(strict_types=1);
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);
}
/**
* GET /auth/callback
* Handle OAuth callback from gnexus-auth.
*/
public function callback()
{
if (session_status() === PHP_SESSION_NONE) {
@session_start();
}
$this->logAuth('callback() called');
try {
$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;
}
}
/**
* POST /auth/logout
*/
public function logout()
{
$service = new AuthService();
$service->logout();
return $this->utils()->response_success();
}
/**
* GET /auth/me
* Return current authenticated user + effective permissions.
*/
public function me()
{
$user = $this->get_current_user();
if (!$user) {
return $this->utils()->response_error('not_found_any_sessions', [], [], 401);
}
$permissions = $this->get_current_permissions();
return $this->utils()->response_success([
'user' => [
'id' => $user['id'],
'gauth_user_id' => $user['gauth_user_id'],
'email' => $user['email'],
'display_name' => $user['display_name'],
'avatar_url' => $user['avatar_url'],
'system_role' => $user['system_role'],
'status' => $user['status'],
],
'permissions' => $permissions,
]);
}
/**
* POST /auth/refresh
* Refresh access token using stored refresh token.
*/
public function refresh()
{
$sessionToken = $_SESSION['shserv_auth_token'] ?? null;
if (!$sessionToken) {
return $this->utils()->response_error('not_found_any_sessions', [], [], 401);
}
$service = new AuthService();
$tokenSet = $service->refreshAccessToken($sessionToken);
if (!$tokenSet) {
return $this->utils()->response_error('session_expired', [], [], 401);
}
return $this->utils()->response_success([
'access_token' => $tokenSet->accessToken,
'expires_in' => $tokenSet->expiresIn,
]);
}
}