diff --git a/public/admin/users/index.php b/public/admin/users/index.php
index b5f9a79e..027146e8 100644
--- a/public/admin/users/index.php
+++ b/public/admin/users/index.php
@@ -20,7 +20,10 @@ if (isset($_SESSION['desktop_auth']) && is_array($_SESSION['desktop_auth'])) {
if (!($accountCheck['allowed'] ?? false)) {
unset($_SESSION['desktop_auth']);
- header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302);
+ $target = (string) ($accountCheck['state'] ?? '') === 'pending'
+ ? '/auth/pending/?username=' . urlencode((string) ($currentAuthUser['username'] ?? ''))
+ : '/auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.'));
+ header('Location: ' . $target, true, 302);
exit;
}
}
diff --git a/public/apps/mining-checker/index.php b/public/apps/mining-checker/index.php
index f31bfea5..bba9bcfa 100644
--- a/public/apps/mining-checker/index.php
+++ b/public/apps/mining-checker/index.php
@@ -20,7 +20,10 @@ if ($auth->isAuthenticated()) {
if (!($accountCheck['allowed'] ?? false)) {
$auth->logout();
- header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302);
+ $target = (string) ($accountCheck['state'] ?? '') === 'pending'
+ ? '/auth/pending/?username=' . urlencode((string) ($currentUser['username'] ?? ''))
+ : '/auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.'));
+ header('Location: ' . $target, true, 302);
exit;
}
}
diff --git a/public/auth/callback/index.php b/public/auth/callback/index.php
index 07043ca4..8912081e 100644
--- a/public/auth/callback/index.php
+++ b/public/auth/callback/index.php
@@ -57,9 +57,11 @@ if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $stat
if (!($accountCheck['allowed'] ?? false)) {
$auth->logout();
- http_response_code(403);
- $title = 'Zugriff gesperrt';
- $message = (string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.');
+ $target = (string) ($accountCheck['state'] ?? '') === 'pending'
+ ? '/auth/pending/?username=' . urlencode($username)
+ : '/auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.'));
+ header('Location: ' . $target, true, 302);
+ exit;
} else {
$auth->establishSession($tokenPayload, $userPayload);
header('Location: ' . $redirectTarget, true, 302);
diff --git a/public/auth/pending/index.php b/public/auth/pending/index.php
new file mode 100644
index 00000000..5e546511
--- /dev/null
+++ b/public/auth/pending/index.php
@@ -0,0 +1,72 @@
+findLatestByUsername($username);
+$status = (string) ($request['status'] ?? 'pending_approval');
+
+$statusMap = [
+ 'pending_approval' => [
+ 'title' => 'Freigabe ausstehend',
+ 'copy' => 'Dein Konto wurde angelegt und wartet aktuell auf die manuelle Freigabe.',
+ 'label' => 'Wartet auf Freigabe',
+ ],
+ 'provisioning_failed' => [
+ 'title' => 'Bearbeitung erforderlich',
+ 'copy' => 'Dein Konto konnte technisch noch nicht vollständig vorbereitet werden. Die Anfrage liegt bereits vor und wird geprüft.',
+ 'label' => 'Technische Prüfung läuft',
+ ],
+ 'approved_active' => [
+ 'title' => 'Freigeschaltet',
+ 'copy' => 'Dein Konto wurde bereits freigeschaltet. Bitte melde dich erneut an.',
+ 'label' => 'Freigeschaltet',
+ ],
+];
+
+$view = $statusMap[$status] ?? [
+ 'title' => 'Freigabestatus',
+ 'copy' => 'Der Status deiner Freigabe wird aktuell verarbeitet.',
+ 'label' => 'In Bearbeitung',
+];
+?>
+
+
+
+
+ = htmlspecialchars($view['title'], ENT_QUOTES) ?>
+
+
+
+
+
+
+
Kusche.Berlin
+
= htmlspecialchars($view['title'], ENT_QUOTES) ?>
+
= htmlspecialchars($view['copy'], ENT_QUOTES) ?>
+
+
+
+
Aktueller Stand
+
Status: = htmlspecialchars($view['label'], ENT_QUOTES) ?>
+
+
Benutzername: = htmlspecialchars((string) ($request['username'] ?? ''), ENT_QUOTES) ?>
+
E-Mail: = htmlspecialchars((string) ($request['email'] ?? ''), ENT_QUOTES) ?>
+
Vorgangs-ID: = htmlspecialchars((string) ($request['id'] ?? ''), ENT_QUOTES) ?>
+
+
+
+
+
+
+
+
diff --git a/public/index.php b/public/index.php
index 6361c62f..87d42989 100644
--- a/public/index.php
+++ b/public/index.php
@@ -23,7 +23,10 @@ if ($auth->isAuthenticated()) {
if (!($accountCheck['allowed'] ?? false)) {
$auth->logout();
- header('Location: /auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.')), true, 302);
+ $target = (string) ($accountCheck['state'] ?? '') === 'pending'
+ ? '/auth/pending/?username=' . urlencode((string) ($currentUser['username'] ?? ''))
+ : '/auth/inactive/?message=' . urlencode((string) ($accountCheck['message'] ?? 'Dieses Konto ist noch nicht freigeschaltet.'));
+ header('Location: ' . $target, true, 302);
exit;
}
}
diff --git a/src/App/AccountGate.php b/src/App/AccountGate.php
index 1733fc13..199af003 100644
--- a/src/App/AccountGate.php
+++ b/src/App/AccountGate.php
@@ -38,28 +38,62 @@ final class AccountGate
];
}
- $user = $this->ldap->getUserByUsername($username, ['uid', 'shadowExpire']);
+ $user = $this->ldap->getUserByUsername($username, ['uid', 'shadowExpire', 'memberOf']);
if (!is_array($user)) {
return [
'allowed' => false,
+ 'state' => 'blocked',
'message' => 'Dieses Konto ist nicht freigeschaltet.',
];
}
$shadowExpire = (string) ($user['shadowexpire'] ?? $user['shadowExpire'] ?? '');
$activeShadowExpire = (string) ($this->config['ldap']['active_shadow_expire'] ?? -1);
+ $pendingGroupDn = strtolower(trim((string) ($this->config['ldap']['pending_group_dn'] ?? '')));
+ $memberOf = $this->normalizeMemberOf($user['memberof'] ?? $user['memberOf'] ?? []);
+ $isPending = $pendingGroupDn !== '' && in_array($pendingGroupDn, $memberOf, true);
- if ($shadowExpire !== $activeShadowExpire) {
+ if ($isPending && $shadowExpire !== $activeShadowExpire) {
return [
'allowed' => false,
+ 'state' => 'pending',
'message' => 'Dieses Konto ist noch nicht freigeschaltet.',
];
}
+ if ($shadowExpire !== '' && $shadowExpire !== $activeShadowExpire) {
+ return [
+ 'allowed' => false,
+ 'state' => 'blocked',
+ 'message' => 'Dieses Konto ist derzeit deaktiviert.',
+ ];
+ }
+
return [
'allowed' => true,
+ 'state' => 'active',
'message' => 'Konto ist aktiv.',
];
}
+
+ /**
+ * @param mixed $value
+ * @return array
+ */
+ private function normalizeMemberOf(mixed $value): array
+ {
+ if (is_string($value)) {
+ $value = [$value];
+ }
+
+ if (!is_array($value)) {
+ return [];
+ }
+
+ return array_values(array_filter(array_map(
+ static fn (mixed $item): string => strtolower(trim((string) $item)),
+ $value
+ )));
+ }
}
diff --git a/src/App/RegistrationService.php b/src/App/RegistrationService.php
index 87485f7d..bceb9669 100644
--- a/src/App/RegistrationService.php
+++ b/src/App/RegistrationService.php
@@ -51,6 +51,14 @@ final class RegistrationService
return $this->store->find($id);
}
+ /**
+ * @return array|null
+ */
+ public function findLatestByUsername(string $username): ?array
+ {
+ return $this->store->findLatestByUsername($username);
+ }
+
/**
* @param array $input
* @return array{success: bool, errors?: array, request?: array}
diff --git a/src/App/RegistrationStore.php b/src/App/RegistrationStore.php
index 956a1375..e950a187 100644
--- a/src/App/RegistrationStore.php
+++ b/src/App/RegistrationStore.php
@@ -42,6 +42,37 @@ final class RegistrationStore
return null;
}
+ /**
+ * @return array|null
+ */
+ public function findLatestByUsername(string $username): ?array
+ {
+ $username = strtolower(trim($username));
+
+ if ($username === '') {
+ return null;
+ }
+
+ $matches = array_values(array_filter(
+ $this->read(),
+ static fn (array $item): bool => strtolower((string) ($item['username'] ?? '')) === $username
+ ));
+
+ if ($matches === []) {
+ return null;
+ }
+
+ usort(
+ $matches,
+ static fn (array $left, array $right): int => strcmp(
+ (string) ($right['created_at'] ?? ''),
+ (string) ($left['created_at'] ?? '')
+ )
+ );
+
+ return $matches[0];
+ }
+
/**
* @return array|null
*/