visual
All checks were successful
Deploy / deploy-staging (push) Successful in 25s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-08-14 01:24:24 +02:00
parent 97e5a2e2da
commit 6de12f164d
5 changed files with 201 additions and 18 deletions

View File

@@ -47,6 +47,62 @@ final class KeycloakAuth
return isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth']);
}
public function ensureAuthenticatedSession(): bool
{
if (!$this->isAuthenticated()) {
return false;
}
if ($this->hasUsableAccessToken()) {
return true;
}
if (!$this->hasUsableRefreshToken()) {
$this->logout();
return false;
}
$refreshToken = (string) ($_SESSION['desktop_auth']['refresh_token'] ?? '');
if ($refreshToken === '') {
$this->logout();
return false;
}
$refreshResponse = $this->postForm(
$this->tokenEndpoint(),
[
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $this->clientId(),
'client_secret' => $this->clientSecret(),
]
);
if (($refreshResponse['success'] ?? false) !== true) {
$this->logout();
return false;
}
/** @var array<string, mixed> $tokenPayload */
$tokenPayload = $refreshResponse['data'];
$accessToken = (string) ($tokenPayload['access_token'] ?? '');
if ($accessToken === '') {
$this->logout();
return false;
}
$userInfo = $this->fetchUserInfo($accessToken);
if (($userInfo['success'] ?? false) !== true) {
$this->logout();
return false;
}
/** @var array<string, mixed> $userPayload */
$userPayload = $userInfo['data'];
$this->establishSession($tokenPayload, $userPayload);
return true;
}
public function shouldShowDesktop(): bool
{
if (!$this->enforceLogin()) {
@@ -385,6 +441,37 @@ final class KeycloakAuth
return is_array($claims) ? $claims : [];
}
private function hasUsableAccessToken(): bool
{
$accessToken = (string) ($_SESSION['desktop_auth']['access_token'] ?? '');
if ($accessToken === '') {
return false;
}
return $this->secondsUntilSessionExpiry('expires_in') > 30;
}
private function hasUsableRefreshToken(): bool
{
$refreshToken = (string) ($_SESSION['desktop_auth']['refresh_token'] ?? '');
if ($refreshToken === '') {
return false;
}
return $this->secondsUntilSessionExpiry('refresh_expires_in') > 30;
}
private function secondsUntilSessionExpiry(string $ttlField): int
{
$authenticatedAt = (int) ($_SESSION['desktop_auth']['authenticated_at'] ?? 0);
$ttl = (int) ($_SESSION['desktop_auth'][$ttlField] ?? 0);
if ($authenticatedAt <= 0 || $ttl <= 0) {
return 0;
}
return ($authenticatedAt + $ttl) - time();
}
/**
* @param array<string, string> $fields
* @return array{success: bool, data?: array<string, mixed>, error?: string}

View File

@@ -14,8 +14,9 @@ final class ModuleHttp
{
$auth = new KeycloakAuth(ConfigLoader::load($projectRoot, 'keycloak'));
$accountGate = new AccountGate(ConfigLoader::load($projectRoot, 'registration'));
$hasAuthenticatedSession = $auth->ensureAuthenticatedSession();
if ($auth->isAuthenticated()) {
if ($hasAuthenticatedSession) {
$currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : [];
$accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? ''));
@@ -35,7 +36,7 @@ final class ModuleHttp
}
}
if (!$auth->shouldShowDesktop()) {
if (!$hasAuthenticatedSession && !$auth->shouldShowDesktop()) {
if ($json) {
self::respondJson(['error' => 'Nicht autorisiert.'], 401);
}