Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

153 рядки
3.9KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Services;
  4. use Core\Response;
  5. use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
  6. class AuthService
  7. {
  8. /**
  9. * When APP_DEBUG is true, auth is bypassed entirely and every request is
  10. * treated as an already-authenticated dev user. Must be false in any
  11. * deployed/production environment — see .env_prod.
  12. */
  13. private static function debugBypass(): bool
  14. {
  15. return filter_var(getenv('APP_DEBUG'), FILTER_VALIDATE_BOOLEAN);
  16. }
  17. private static function devUser(): array
  18. {
  19. return [
  20. 'preferred_username' => 'dev-admin',
  21. 'email' => 'dev-admin@localhost',
  22. 'name' => 'Dev Admin (APP_DEBUG bypass)',
  23. ];
  24. }
  25. private static function config(): array
  26. {
  27. static $config = null;
  28. if ($config === null) {
  29. $config = require __DIR__ . '/../../config/auth.php';
  30. }
  31. return $config['keycloak'];
  32. }
  33. public static function provider(): Keycloak
  34. {
  35. $cfg = self::config();
  36. return new Keycloak([
  37. 'authServerUrl' => rtrim($cfg['base_url'], '/'),
  38. 'realm' => $cfg['realm'],
  39. 'clientId' => $cfg['client_id'],
  40. 'clientSecret' => $cfg['client_secret'],
  41. 'redirectUri' => $cfg['redirect_uri'],
  42. ]);
  43. }
  44. /**
  45. * Decode user claims from the access token JWT payload.
  46. * Avoids calling the userinfo endpoint, which Keycloak may return as a
  47. * signed JWT (application/jwt) rather than JSON — causing decryption errors.
  48. *
  49. * @return array<string, mixed>
  50. */
  51. public static function claimsFromToken(string $jwt): array
  52. {
  53. $parts = explode('.', $jwt);
  54. if (count($parts) < 2) {
  55. return [];
  56. }
  57. $payload = base64_decode(strtr($parts[1], '-_', '+/'), true);
  58. if ($payload === false) {
  59. return [];
  60. }
  61. $data = json_decode($payload, true);
  62. return is_array($data) ? $data : [];
  63. }
  64. public static function requireLogin(): ?Response
  65. {
  66. if (self::debugBypass()) {
  67. return null;
  68. }
  69. if (!self::isLoggedIn()) {
  70. $_SESSION['auth_return_to'] = $_SERVER['REQUEST_URI'] ?? '/';
  71. return Response::redirect('/auth/login');
  72. }
  73. return null;
  74. }
  75. public static function isLoggedIn(): bool
  76. {
  77. if (self::debugBypass()) {
  78. return true;
  79. }
  80. return !empty($_SESSION['auth_user']);
  81. }
  82. public static function getCurrentUser(): array
  83. {
  84. if (self::debugBypass() && empty($_SESSION['auth_user'])) {
  85. return self::devUser();
  86. }
  87. return $_SESSION['auth_user'] ?? [];
  88. }
  89. public static function getCurrentUsername(): string
  90. {
  91. $user = self::getCurrentUser();
  92. return $user['preferred_username'] ?? $user['email'] ?? '';
  93. }
  94. public static function storeUser(array $userInfo): void
  95. {
  96. $_SESSION['auth_user'] = $userInfo;
  97. }
  98. public static function clearSession(): void
  99. {
  100. $_SESSION = [];
  101. if (ini_get('session.use_cookies')) {
  102. $params = session_get_cookie_params();
  103. setcookie(
  104. session_name(),
  105. '',
  106. time() - 42000,
  107. $params['path'],
  108. $params['domain'],
  109. $params['secure'],
  110. $params['httponly']
  111. );
  112. }
  113. session_destroy();
  114. }
  115. public static function logoutUrl(): string
  116. {
  117. $cfg = self::config();
  118. $base = rtrim($cfg['base_url'], '/');
  119. $realm = $cfg['realm'];
  120. $postLogout = urlencode($cfg['post_logout_redirect_uri']);
  121. return "{$base}/realms/{$realm}/protocol/openid-connect/logout?redirect_uri={$postLogout}";
  122. }
  123. }

Powered by TurnKey Linux.