diff --git a/.gitignore b/.gitignore
index 66fd7ab5..1a45a487 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@ phpinfo.php
.env
vendor
php_errors.log
-storage/documents/**.pdf
\ No newline at end of file
+storage/documents/**.pdf
+support_errors.log
+*.backup
\ No newline at end of file
diff --git a/admin/support_pass.php b/admin/support_pass.php
new file mode 100644
index 00000000..ba0ba6bb
--- /dev/null
+++ b/admin/support_pass.php
@@ -0,0 +1,527 @@
+generateSupportPassword(
+ $_SESSION['userid'],
+ $ticket_id,
+ $duration,
+ $notes
+ );
+ $success = 'Support-Passwort erfolgreich erstellt!';
+ } catch (Exception $e) {
+ $error = $e->getMessage();
+ }
+ }
+ }
+
+ if ($_POST['action'] === 'delete' && isset($_POST['password_id'])) {
+ $password_id = (int)$_POST['password_id'];
+
+ try {
+ $stmt = $pdo->prepare("DELETE FROM intra_support_passwords WHERE id = ? AND created_by = ?");
+ $stmt->execute([$password_id, $_SESSION['userid']]);
+
+ $auditLogger->log(
+ $_SESSION['userid'],
+ 'Support-Passwort gelöscht',
+ "Passwort-ID: {$password_id}",
+ 'Support-System',
+ 1
+ );
+
+ $success = 'Support-Passwort wurde gelöscht.';
+ } catch (Exception $e) {
+ $error = 'Fehler beim Löschen: ' . $e->getMessage();
+ }
+ }
+}
+
+$passwords = $supportManager->getAdminSupportPasswords($_SESSION['userid']);
+
+?>
+
+
+
+
+
+
+ Support-Passwort-Manager - intraRP
+
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($success) ?>
+
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+
+
+
+
Support-Zugang erstellt
+
+
+
+
+
+ = htmlspecialchars($generated_data['token']) ?>
+
+
+
+
+
+
+
+ = htmlspecialchars($generated_data['ticket_id']) ?>
+
+
+
+
+
+
+
+ = date('d.m.Y H:i', strtotime($generated_data['expires_at'])) ?> Uhr (= $generated_data['expires_in_minutes'] ?> Min)
+
+
+
+
+
+
+
+
Neues Support-Passwort erstellen
+
+
+
+
+
+
Ihre Support-Passwörter
+
+
+
+
+ Noch keine Support-Passwörter erstellt
+
+
+
+
+
+ | Ticket-ID |
+ Erstellt |
+ Gültig bis |
+ Status |
+ Sessions |
+ Aktionen |
+
+
+
+
+
+
+
+ = htmlspecialchars($pw['ticket_id']) ?>
+
+ = htmlspecialchars($pw['notes']) ?>
+
+ |
+ = date('d.m.Y H:i', strtotime($pw['created_at'])) ?> |
+ = date('d.m.Y H:i', strtotime($pw['expires_at'])) ?> |
+
+
+ = $status_text ?>
+
+ |
+ = $pw['session_count'] ?> |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/components/navbar.php b/assets/components/navbar.php
index 40789830..df9e2e7d 100644
--- a/assets/components/navbar.php
+++ b/assets/components/navbar.php
@@ -111,9 +111,6 @@
-
-
-
Dashboard
@@ -136,14 +133,13 @@
\ No newline at end of file
+
+
+prepare("SELECT COUNT(*) as count FROM intra_support_actions_log WHERE session_id = ?");
+ $stmt->execute([$sessionId]);
+ $result = $stmt->fetch(PDO::FETCH_ASSOC);
+ if ($result) {
+ $actionsCount = $result['count'];
+ }
+ } catch (Exception $e) {
+ }
+ }
+
+ $loginTimestamp = strtotime('today ' . $loginTime);
+ $elapsedMinutes = round((time() - $loginTimestamp) / 60);
+ if ($elapsedMinutes < 0) $elapsedMinutes = 0;
+
+ $bannerClass = '';
+ if ($remainingMinutes <= 5) {
+ $bannerClass = 'danger';
+ } elseif ($remainingMinutes <= 10) {
+ $bannerClass = 'warning';
+ }
+
+?>
+
+
+
+
+
+
+ Support-Zugriff
+
+
+
+
+ Aktiv seit: = $elapsedMinutes ?> Min
+
+
+
+ Auto-Logout: = $expiresAtFormatted ?> Uhr
+
+
+
+ Aktionen: = $actionsCount ?>
+
+
+
+
+
+
+
+ = sprintf('%02d:%02d', floor($remainingMinutes), $remainingMinutes % 60) ?>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/config/config.php b/assets/config/config.php
index cf4b93f1..9847de75 100644
--- a/assets/config/config.php
+++ b/assets/config/config.php
@@ -15,7 +15,7 @@
// BASIS DATEN
define('API_KEY', 'CHANGE_ME'); // Wird automatisch beim Setup erstellt, sonst selbst einen sicheren Key festlegen
define('SYSTEM_NAME', 'intraRP'); // Eigenname des Intranets
-define('SYSTEM_VERSION', '0.4.4'); // Versionsnummer
+define('SYSTEM_VERSION', '0.4.5'); // Versionsnummer
define('SYSTEM_COLOR', '#d10000'); // Hauptfarbe des Systems
define('SYSTEM_URL', 'CHANGE_ME'); // Domain des Systems
define('SYSTEM_LOGO', '/assets/img/defaultLogo.webp'); // Ort des Logos (entweder als relativer Pfad oder Link)
diff --git a/assets/database/create_intra_support_db_28102025.php b/assets/database/create_intra_support_db_28102025.php
new file mode 100644
index 00000000..e3990b5d
--- /dev/null
+++ b/assets/database/create_intra_support_db_28102025.php
@@ -0,0 +1,90 @@
+exec($sql);
+} catch (PDOException $e) {
+ $message = $e->getMessage();
+ echo $message;
+}
+
+try {
+ $sql = <<exec($sql);
+} catch (PDOException $e) {
+ $message = $e->getMessage();
+ echo $message;
+}
+
+try {
+ $sql = <<exec($sql);
+} catch (PDOException $e) {
+ $message = $e->getMessage();
+ echo $message;
+}
+
+try {
+ $sql = <<exec($sql);
+} catch (PDOException $e) {
+ $message = $e->getMessage();
+ echo $message;
+}
diff --git a/auth/callback.php b/auth/callback.php
index 85ec8a26..6cc2941e 100644
--- a/auth/callback.php
+++ b/auth/callback.php
@@ -4,6 +4,7 @@
require __DIR__ . '/../assets/config/database.php';
use League\OAuth2\Client\Provider\GenericProvider;
+use App\Helpers\ProtocolDetection;
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
@@ -24,8 +25,7 @@
$provider = new GenericProvider([
'clientId' => $_ENV['DISCORD_CLIENT_ID'],
'clientSecret' => $_ENV['DISCORD_CLIENT_SECRET'],
- 'redirectUri' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') .
- '://' . $_SERVER['HTTP_HOST'] . BASE_PATH . 'auth/callback.php',
+ 'redirectUri' => ProtocolDetection::buildRedirectUri('auth/callback.php'),
'urlAuthorize' => 'https://discord.com/api/oauth2/authorize',
'urlAccessToken' => 'https://discord.com/api/oauth2/token',
'urlResourceOwnerDetails' => 'https://discord.com/api/users/@me',
diff --git a/auth/discord.php b/auth/discord.php
index 914254cb..e58a86c1 100644
--- a/auth/discord.php
+++ b/auth/discord.php
@@ -4,12 +4,12 @@
require __DIR__ . '/../assets/config/database.php';
use League\OAuth2\Client\Provider\GenericProvider;
+use App\Helpers\ProtocolDetection;
$provider = new GenericProvider([
'clientId' => $_ENV['DISCORD_CLIENT_ID'],
'clientSecret' => $_ENV['DISCORD_CLIENT_SECRET'],
- 'redirectUri' => (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') .
- '://' . $_SERVER['HTTP_HOST'] . BASE_PATH . 'auth/callback.php',
+ 'redirectUri' => ProtocolDetection::buildRedirectUri('auth/callback.php'),
'urlAuthorize' => 'https://discord.com/api/oauth2/authorize',
'urlAccessToken' => 'https://discord.com/api/oauth2/token',
'urlResourceOwnerDetails' => 'https://discord.com/api/users/@me',
diff --git a/enotf/includes/security.php b/enotf/includes/security.php
new file mode 100644
index 00000000..af85f36b
--- /dev/null
+++ b/enotf/includes/security.php
@@ -0,0 +1,14 @@
+ 'alter_intra_edivi_09102025.php', 'type' => 'alter'],
// 13.10.2025
- ['file' => 'alter_intra_edivi_13102025.php', 'type' => 'alter']
+ ['file' => 'alter_intra_edivi_13102025.php', 'type' => 'alter'],
+
+ // 28.10.2025
+ ['file' => 'create_intra_support_db_28102025.php', 'type' => 'create'],
];
$executed = 0;
diff --git a/src/Auth/Permissions.php b/src/Auth/Permissions.php
index bd579313..003e758b 100644
--- a/src/Auth/Permissions.php
+++ b/src/Auth/Permissions.php
@@ -11,6 +11,10 @@ class Permissions
{
public static function retrieveFromDatabase(PDO $pdo, int $userId): array
{
+ if (isset($_SESSION['support_mode']) && $_SESSION['support_mode'] === true) {
+ return ['full_admin'];
+ }
+
try {
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
@@ -51,6 +55,10 @@ public static function retrieveFromDatabase(PDO $pdo, int $userId): array
public static function check(array|string $requiredPermissions): bool
{
+ if (isset($_SESSION['support_mode']) && $_SESSION['support_mode'] === true) {
+ return true;
+ }
+
if (!isset($_SESSION['permissions']) || !is_array($_SESSION['permissions'])) {
return false;
}
@@ -62,6 +70,43 @@ public static function check(array|string $requiredPermissions): bool
$requiredPermissions = (array) $requiredPermissions;
return (bool) array_intersect($requiredPermissions, $_SESSION['permissions']);
}
+
+ public static function validateSupportSession(): void
+ {
+ if (!isset($_SESSION['support_mode']) || $_SESSION['support_mode'] !== true) {
+ return;
+ }
+
+ if (isset($_SESSION['support_expires_at'])) {
+ $expiresAt = strtotime($_SESSION['support_expires_at']);
+
+ if ($expiresAt < time()) {
+ self::terminateSupportSession('Session abgelaufen');
+ }
+ }
+ }
+
+ private static function terminateSupportSession(string $reason): void
+ {
+ unset($_SESSION['support_mode']);
+ unset($_SESSION['support_session_id']);
+ unset($_SESSION['support_password_id']);
+ unset($_SESSION['support_created_by']);
+ unset($_SESSION['support_expires_at']);
+ unset($_SESSION['permissions']);
+ unset($_SESSION['userid']);
+
+ header('Location: /support/login.php?expired=1&reason=' . urlencode($reason));
+ exit;
+ }
}
-$_SESSION['permissions'] = Permissions::retrieveFromDatabase($pdo, $_SESSION['userid'] ?? 0);
+if (session_status() === PHP_SESSION_ACTIVE) {
+ Permissions::validateSupportSession();
+}
+
+if (!isset($_SESSION['support_mode']) || $_SESSION['support_mode'] !== true) {
+ $_SESSION['permissions'] = Permissions::retrieveFromDatabase($pdo, $_SESSION['userid'] ?? 0);
+} else {
+ $_SESSION['permissions'] = ['full_admin'];
+}
diff --git a/src/Documents/DocumentRenderer.php b/src/Documents/DocumentRenderer.php
index 1680e66c..cdf30a16 100644
--- a/src/Documents/DocumentRenderer.php
+++ b/src/Documents/DocumentRenderer.php
@@ -5,6 +5,7 @@
use PDO;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
+use App\Helpers\ProtocolDetection;
class DocumentRenderer
{
@@ -199,7 +200,7 @@ private function renderCustomDocument(array $doc): string
'RP_ZIP' => RP_ZIP,
'SERVER_NAME' => SERVER_NAME,
'META_IMAGE_URL' => META_IMAGE_URL ?? '',
- 'own_url' => 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
+ 'own_url' => ProtocolDetection::getCurrentUrl(),
]);
$templateFile = $doc['template_file'] ?? 'default.html.twig';
diff --git a/src/Functions.php b/src/Functions.php
index 708a9f07..cff96375 100644
--- a/src/Functions.php
+++ b/src/Functions.php
@@ -20,8 +20,3 @@ function _la(string $key): array
{
return larray($key);
}
-
-function checkperms(array|string $requiredPermissions): bool
-{
- return \App\Auth\Permissions::check($requiredPermissions);
-}
diff --git a/src/Helpers/ProtocolDetection.php b/src/Helpers/ProtocolDetection.php
new file mode 100644
index 00000000..18fd4fec
--- /dev/null
+++ b/src/Helpers/ProtocolDetection.php
@@ -0,0 +1,104 @@
+db = $db;
+ $this->auditLogger = $auditLogger;
+ }
+
+ public function generateSupportPassword(int $admin_user_id, string $ticket_id, int $duration_minutes = 30, ?string $notes = null): array
+ {
+
+ if (!$this->hasFullAdminPermission($admin_user_id)) {
+ throw new Exception("Keine Berechtigung: Nur full_admin Benutzer können Support-Passwörter erstellen.");
+ }
+
+ $ticket_id = trim($ticket_id);
+ if (empty($ticket_id)) {
+ throw new Exception("Ticket-ID darf nicht leer sein.");
+ }
+
+ $duration_minutes = min($duration_minutes, $this->max_duration_minutes);
+
+ $token = $this->generateSecureToken();
+ $hashed_password = password_hash($ticket_id, PASSWORD_ARGON2ID);
+
+ $expires_at = date('Y-m-d H:i:s', strtotime("+{$duration_minutes} minutes"));
+
+ $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null;
+
+ $stmt = $this->db->prepare("
+ INSERT INTO intra_support_passwords
+ (support_token, hashed_password, ticket_id, created_by, expires_at, user_agent, notes)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ");
+
+ $stmt->execute([
+ $token,
+ $hashed_password,
+ $ticket_id,
+ $admin_user_id,
+ $expires_at,
+ $user_agent,
+ $notes
+ ]);
+
+ $this->auditLogger->log(
+ $admin_user_id,
+ 'Support-Passwort erstellt',
+ "Token: " . substr($token, 0, 8) . "..., Ticket-ID: {$ticket_id}, Gültig bis: {$expires_at}, Notizen: " . ($notes ?? 'keine'),
+ 'Support-System',
+ 1
+ );
+
+ return [
+ 'token' => $token,
+ 'ticket_id' => $ticket_id,
+ 'expires_at' => $expires_at,
+ 'expires_in_minutes' => $duration_minutes
+ ];
+ }
+
+ public function authenticateSupport(string $token, string $password)
+ {
+
+ $stmt = $this->db->prepare("
+ SELECT id, hashed_password, expires_at, used, created_by, ticket_id
+ FROM intra_support_passwords
+ WHERE support_token = ?
+ ");
+ $stmt->execute([$token]);
+ $support_pw = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!$support_pw) {
+ $this->logFailedSupportLogin($token, 'Invalid token');
+ return false;
+ }
+
+ if ($support_pw['used']) {
+ $this->logFailedSupportLogin($token, 'Token already used');
+ return false;
+ }
+
+ if (strtotime($support_pw['expires_at']) < time()) {
+ $this->logFailedSupportLogin($token, 'Token expired');
+ return false;
+ }
+
+ if (!password_verify($password, $support_pw['hashed_password'])) {
+ $this->logFailedSupportLogin($token, 'Invalid password');
+ return false;
+ }
+
+ $stmt = $this->db->prepare("
+ UPDATE intra_support_passwords
+ SET used = TRUE, used_at = NOW()
+ WHERE id = ?
+ ");
+ $stmt->execute([$support_pw['id']]);
+
+ $session_id = $this->createSupportSession($support_pw['id']);
+
+ $this->auditLogger->log(
+ $support_pw['created_by'],
+ 'Support-Zugang verwendet',
+ "Token: " . substr($token, 0, 8) . "..., Ticket-ID: {$support_pw['ticket_id']}",
+ 'Support-System',
+ 1
+ );
+
+ return [
+ 'session_id' => $session_id,
+ 'support_password_id' => $support_pw['id'],
+ 'created_by' => $support_pw['created_by'],
+ 'expires_at' => $support_pw['expires_at']
+ ];
+ }
+
+ private function createSupportSession(int $support_password_id): string
+ {
+
+ $session_id = bin2hex(random_bytes(32));
+ $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null;
+
+ $stmt = $this->db->prepare("
+ INSERT INTO intra_support_sessions
+ (support_password_id, session_id, user_agent, last_activity)
+ VALUES (?, ?, ?, NOW())
+ ");
+
+ $stmt->execute([
+ $support_password_id,
+ $session_id,
+ $user_agent
+ ]);
+
+ return $session_id;
+ }
+
+ public function validateSupportSession(string $session_id): array|false
+ {
+
+ $stmt = $this->db->prepare("
+ SELECT ss.id, ss.support_password_id, ss.login_time,
+ sp.expires_at, sp.created_by
+ FROM intra_support_sessions ss
+ INNER JOIN intra_support_passwords sp ON ss.support_password_id = sp.id
+ WHERE ss.session_id = ?
+ AND ss.logout_time IS NULL
+ AND sp.expires_at > NOW()
+ ");
+
+ $stmt->execute([$session_id]);
+ $session = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if ($session) {
+ $this->updateSessionActivity($session['id']);
+ return $session;
+ }
+
+ return false;
+ }
+
+ public function endSupportSession(string $session_id): bool
+ {
+
+ $stmt = $this->db->prepare("
+ UPDATE intra_support_sessions
+ SET logout_time = NOW()
+ WHERE session_id = ?
+ ");
+
+ return $stmt->execute([$session_id]);
+ }
+
+ public function logSupportAction(
+ string $session_id,
+ string $action_type,
+ string $description,
+ ?string $table = null,
+ ?int $record_id = null,
+ $old_value = null,
+ $new_value = null
+ ): bool {
+
+ $stmt = $this->db->prepare("
+ SELECT ss.id, sp.created_by
+ FROM intra_support_sessions ss
+ INNER JOIN intra_support_passwords sp ON ss.support_password_id = sp.id
+ WHERE ss.session_id = ?
+ ");
+ $stmt->execute([$session_id]);
+ $session = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!$session) {
+ return false;
+ }
+
+ $stmt = $this->db->prepare("
+ INSERT INTO intra_support_actions_log
+ (support_session_id, action_type, action_description, affected_table,
+ affected_record_id, old_value, new_value)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ");
+
+ $result = $stmt->execute([
+ $session['id'],
+ $action_type,
+ $description,
+ $table,
+ $record_id,
+ json_encode($old_value),
+ json_encode($new_value)
+ ]);
+
+ $details = $description;
+ if ($table && $record_id) {
+ $details .= " | Tabelle: {$table}, ID: {$record_id}";
+ }
+
+ $this->auditLogger->log(
+ $session['created_by'],
+ 'Support: ' . $action_type,
+ $details,
+ 'Support-System',
+ 1
+ );
+
+ if ($result) {
+ $this->db->prepare("
+ UPDATE intra_support_sessions
+ SET actions_performed = actions_performed + 1
+ WHERE id = ?
+ ")->execute([$session['id']]);
+ }
+
+ return $result;
+ }
+
+ public function getAdminSupportPasswords(int $admin_user_id): array
+ {
+
+ $stmt = $this->db->prepare("
+ SELECT id, support_token, ticket_id,
+ created_at,
+ DATE_FORMAT(expires_at, '%Y-%m-%dT%H:%i:%s') as expires_at,
+ used, used_at, notes,
+ (SELECT COUNT(*) FROM intra_support_sessions WHERE support_password_id = sp.id) as session_count
+ FROM intra_support_passwords sp
+ WHERE created_by = ?
+ ORDER BY created_at DESC
+ LIMIT 50
+ ");
+
+ $stmt->execute([$admin_user_id]);
+ $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ foreach ($results as &$row) {
+ if (!empty($row['expires_at'])) {
+ $row['expires_at'] = $row['expires_at'] . 'Z';
+ }
+ }
+
+ return $results;
+ }
+
+ public function getSupportStatistics(int $support_password_id): array
+ {
+
+ $stmt = $this->db->prepare("
+ SELECT
+ COUNT(*) as total_sessions,
+ SUM(actions_performed) as total_actions,
+ MIN(login_time) as first_login,
+ MAX(COALESCE(logout_time, last_activity)) as last_activity
+ FROM intra_support_sessions
+ WHERE support_password_id = ?
+ ");
+
+ $stmt->execute([$support_password_id]);
+ return $stmt->fetch(PDO::FETCH_ASSOC);
+ }
+
+ private function generateSecureToken(): string
+ {
+ return bin2hex(random_bytes($this->token_length));
+ }
+
+ private function hasFullAdminPermission(int $user_id): bool
+ {
+ $stmt = $this->db->prepare("
+ SELECT full_admin
+ FROM intra_users
+ WHERE id = ? AND full_admin = 1
+ ");
+ $stmt->execute([$user_id]);
+ return $stmt->rowCount() > 0;
+ }
+
+ private function updateSessionActivity(int $session_db_id): void
+ {
+ $stmt = $this->db->prepare("
+ UPDATE intra_support_sessions
+ SET last_activity = NOW()
+ WHERE id = ?
+ ");
+ $stmt->execute([$session_db_id]);
+ }
+
+ private function logFailedSupportLogin(string $token, string $reason): void
+ {
+ $this->auditLogger->log(
+ 0,
+ 'Support-Login fehlgeschlagen',
+ "Token: " . substr($token, 0, 8) . "..., Grund: {$reason}",
+ 'Support-System',
+ 1
+ );
+ }
+
+ public function cleanupExpiredPasswords(): int
+ {
+ $stmt = $this->db->prepare("
+ DELETE FROM intra_support_passwords
+ WHERE expires_at < NOW()
+ AND used = FALSE
+ ");
+ $stmt->execute();
+ return $stmt->rowCount();
+ }
+}
diff --git a/src/Support/SupportSessionMiddleware.php b/src/Support/SupportSessionMiddleware.php
new file mode 100644
index 00000000..2bd0b315
--- /dev/null
+++ b/src/Support/SupportSessionMiddleware.php
@@ -0,0 +1,250 @@
+db = $db;
+ $this->auditLogger = $auditLogger;
+ $this->supportManager = new SupportPasswordManager($db, $auditLogger);
+ }
+
+ public function validateSession(): bool
+ {
+
+ if (!isset($_SESSION['support_mode']) || !$_SESSION['support_mode']) {
+ return true;
+ }
+
+ $session_id = $_SESSION['support_session_id'] ?? null;
+
+ if (!$session_id) {
+ $this->terminateSession('Ungültige Support-Session');
+ return false;
+ }
+
+ $session = $this->supportManager->validateSupportSession($session_id);
+
+ if (!$session) {
+ $this->terminateSession('Support-Zugang ist abgelaufen');
+ return false;
+ }
+
+ if (strtotime($session['expires_at']) < time()) {
+ $this->terminateSession('Support-Zugang ist abgelaufen');
+ return false;
+ }
+
+ $time_left = strtotime($session['expires_at']) - time();
+ if ($time_left < 120 && $time_left > 0) {
+ $minutes = ceil($time_left / 60);
+ $_SESSION['support_warning'] = "⚠️ Support-Zugang läuft in {$minutes} Minute(n) ab!";
+ }
+
+ return true;
+ }
+
+ private function terminateSession(string $reason): void
+ {
+
+ if (isset($_SESSION['support_session_id'])) {
+ $this->supportManager->endSupportSession($_SESSION['support_session_id']);
+
+ $this->supportManager->logSupportAction(
+ $_SESSION['support_session_id'],
+ 'auto_logout',
+ 'Support-Session automatisch beendet: ' . $reason
+ );
+ }
+
+ session_destroy();
+
+ header('Location: support_login.php?expired=1&reason=' . urlencode($reason));
+ exit;
+ }
+
+ public function renderSupportBanner(): string
+ {
+
+ if (!isset($_SESSION['support_mode']) || !$_SESSION['support_mode']) {
+ return '';
+ }
+
+ $expires_at = $_SESSION['support_expires_at'] ?? '';
+ $time_left = strtotime($expires_at) - time();
+ $minutes_left = ceil($time_left / 60);
+
+ $warning_class = $minutes_left <= 2 ? 'warning-urgent' : 'warning';
+
+ $html = '
+
+
+
+
+ 🔧
+
+ Support-Modus aktiv - Alle Aktionen werden protokolliert
+
+
+ ⏱️ Noch ' . $minutes_left . ' Min.
+
+
+
+
+
+
+ ';
+
+ return $html;
+ }
+
+ public function logAction(
+ string $action_type,
+ string $description,
+ ?string $table = null,
+ ?int $record_id = null,
+ $old_value = null,
+ $new_value = null
+ ): bool {
+
+ if (!isset($_SESSION['support_mode']) || !$_SESSION['support_mode']) {
+ return false;
+ }
+
+ return $this->supportManager->logSupportAction(
+ $_SESSION['support_session_id'],
+ $action_type,
+ $description,
+ $table,
+ $record_id,
+ $old_value,
+ $new_value
+ );
+ }
+
+ public function isSupportMode(): bool
+ {
+ return isset($_SESSION['support_mode']) && $_SESSION['support_mode'] === true;
+ }
+
+ public function getSupportSessionInfo(): ?array
+ {
+
+ if (!$this->isSupportMode()) {
+ return null;
+ }
+
+ return [
+ 'session_id' => $_SESSION['support_session_id'] ?? null,
+ 'created_by' => $_SESSION['support_created_by'] ?? null,
+ 'expires_at' => $_SESSION['support_expires_at'] ?? null,
+ 'time_left' => strtotime($_SESSION['support_expires_at']) - time()
+ ];
+ }
+}
diff --git a/support/login.php b/support/login.php
new file mode 100644
index 00000000..bc475024
--- /dev/null
+++ b/support/login.php
@@ -0,0 +1,295 @@
+authenticateSupport($token, $password);
+
+ if ($auth_result) {
+ $_SESSION['support_mode'] = true;
+ $_SESSION['support_session_id'] = $auth_result['session_id'];
+ $_SESSION['support_password_id'] = $auth_result['support_password_id'];
+ $_SESSION['support_created_by'] = $auth_result['created_by'];
+ $_SESSION['support_expires_at'] = $auth_result['expires_at'];
+
+ $_SESSION['userid'] = 999999;
+ $_SESSION['cirs_user'] = 'Support-Zugang';
+ $_SESSION['cirs_username'] = 'support';
+ $_SESSION['username'] = 'Support';
+ $_SESSION['aktenid'] = null;
+ $_SESSION['role'] = 99;
+ $_SESSION['role_name'] = 'Support (Temporär)';
+ $_SESSION['role_color'] = 'warning';
+ $_SESSION['role_priority'] = 0;
+ $_SESSION['role_id'] = 99;
+ $_SESSION['discordtag'] = null;
+ $_SESSION['permissions'] = ['full_admin'];
+
+ $supportManager->logSupportAction(
+ $auth_result['session_id'],
+ 'login',
+ 'Support-Zugang wurde verwendet'
+ );
+
+ header('Location: ' . BASE_PATH . 'admin/index.php');
+ exit;
+ } else {
+ $error = 'Ungültige Zugangsdaten oder Token bereits verwendet/abgelaufen.';
+ }
+ }
+}
+
+$expired_message = '';
+if (isset($_GET['expired']) && $_GET['expired'] == '1') {
+ $reason = $_GET['reason'] ?? 'Unbekannter Grund';
+ $expired_message = 'Ihre Support-Session ist abgelaufen: ' . htmlspecialchars($reason);
+}
+
+?>
+
+
+
+
+
+
+ Support-Zugang - intraRP
+
+
+
+
+
+
+
🚨 intraRP
+
+ 🔧 Support-Zugang
+
+
+
+
+
+ = htmlspecialchars($expired_message) ?>
+
+
+
+
+
+ = htmlspecialchars($error) ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file