visual
This commit is contained in:
@@ -473,6 +473,16 @@
|
||||
source: requestUrl,
|
||||
});
|
||||
}
|
||||
if (response.status === 401) {
|
||||
emitDebug({
|
||||
type: 'request:unauthorized',
|
||||
method: requestOptions.method || 'GET',
|
||||
url: requestUrl,
|
||||
redirect_to: '/auth/keycloak',
|
||||
});
|
||||
window.location.href = '/auth/keycloak';
|
||||
throw new Error('Nicht autorisiert. Weiterleitung zum Login.');
|
||||
}
|
||||
if (!response.ok) {
|
||||
const context = payload && payload.context && typeof payload.context === 'object' ? payload.context : null;
|
||||
const detail = context
|
||||
|
||||
@@ -1581,7 +1581,8 @@ body .tray-pill.tray-pill-status--unconfigured {
|
||||
@media (max-width: 1100px) {
|
||||
.desktop-stage {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120px;
|
||||
padding-bottom: calc(var(--taskbar-height) + var(--taskbar-bottom) + 16px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.desktop-icons {
|
||||
@@ -1612,34 +1613,92 @@ body .tray-pill.tray-pill-status--unconfigured {
|
||||
|
||||
.start-menu {
|
||||
position: fixed;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 88px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
bottom: calc(var(--taskbar-height) + var(--taskbar-bottom) + 8px);
|
||||
transform: none;
|
||||
width: auto;
|
||||
max-width: none;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
grid-template-columns: 1fr;
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.start-menu-sidebar {
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
min-width: 0;
|
||||
padding: 16px 12px 12px;
|
||||
}
|
||||
|
||||
.start-menu-selection-area {
|
||||
min-width: 0;
|
||||
padding: 16px 12px 20px;
|
||||
}
|
||||
|
||||
.start-menu-function-list,
|
||||
.start-menu-selection-list,
|
||||
.start-menu-group,
|
||||
.start-menu-group-list {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.start-menu-selection-list {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.start-menu-entry {
|
||||
grid-template-columns: 40px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.start-menu-entry-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.window-layer {
|
||||
position: static;
|
||||
inset: auto;
|
||||
margin-top: 24px;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
position: fixed;
|
||||
inset: 0 0 calc(var(--taskbar-height) + var(--taskbar-bottom) + 6px) 0;
|
||||
margin-top: 0;
|
||||
display: block;
|
||||
z-index: 95;
|
||||
}
|
||||
|
||||
.window {
|
||||
position: relative;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
left: auto !important;
|
||||
top: auto !important;
|
||||
height: 100% !important;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.window-header-spacer {
|
||||
width: 48px;
|
||||
flex-basis: 48px;
|
||||
}
|
||||
|
||||
.window-content,
|
||||
.window-content--module,
|
||||
.window-content--embedded,
|
||||
.window-module-host,
|
||||
.window-app-frame {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.window-app-frame {
|
||||
@@ -1662,6 +1721,22 @@ body .tray-pill.tray-pill-status--unconfigured {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.taskbar {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
min-height: 56px;
|
||||
padding: 8px 10px;
|
||||
gap: 8px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.taskbar-apps {
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.desktop-debug-toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -17,8 +17,9 @@ $keycloakConfig = ConfigLoader::load($projectRoot, 'keycloak');
|
||||
$registrationConfig = ConfigLoader::load($projectRoot, 'registration');
|
||||
$auth = new KeycloakAuth($keycloakConfig);
|
||||
$accountGate = new AccountGate($registrationConfig);
|
||||
$hasAuthenticatedSession = $auth->ensureAuthenticatedSession();
|
||||
|
||||
if (!$auth->isAuthenticated()) {
|
||||
if (!$hasAuthenticatedSession) {
|
||||
$statusRedirect = AuthStatusRedirect::consume();
|
||||
|
||||
if ($statusRedirect !== null) {
|
||||
@@ -27,7 +28,7 @@ if (!$auth->isAuthenticated()) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($auth->isAuthenticated()) {
|
||||
if ($hasAuthenticatedSession) {
|
||||
$currentUser = is_array($_SESSION['desktop_auth']['user'] ?? null) ? $_SESSION['desktop_auth']['user'] : [];
|
||||
$accountCheck = $accountGate->checkUsername((string) ($currentUser['username'] ?? ''));
|
||||
|
||||
@@ -42,7 +43,7 @@ if ($auth->isAuthenticated()) {
|
||||
}
|
||||
|
||||
if (
|
||||
!$auth->isAuthenticated()
|
||||
!$hasAuthenticatedSession
|
||||
&& $auth->isConfigured()
|
||||
&& empty($_SESSION['desktop_silent_sso_attempted'])
|
||||
) {
|
||||
@@ -51,6 +52,15 @@ if (
|
||||
exit;
|
||||
}
|
||||
|
||||
if (
|
||||
!$hasAuthenticatedSession
|
||||
&& $auth->isConfigured()
|
||||
&& $auth->enforceLogin()
|
||||
) {
|
||||
header('Location: /auth/keycloak', true, 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
$app = new App($projectRoot);
|
||||
$desktopPayload = $app->desktopPayload();
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user