prepare intranet for first Dokploy deployment

This commit is contained in:
2026-08-26 12:59:51 +02:00
parent 5cfe688bdc
commit 29dfb85fef
1321 changed files with 227234 additions and 0 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
<?php /* Name: login.php
Beschreibung: Login-Seite (Authorisierung) */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
$sql = "SELECT
u.*,
l.password_hash AS password_hash
FROM
user_general_information AS u
INNER JOIN
user_login AS l ON u.user_id = l.user_id
WHERE
u.mail = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_POST["email"]]);
$user = $stmt->fetch();
if ($user) {
if (isset($user['password_hash']) && password_verify($_POST["password"], $user["password_hash"])) {
session_start();
session_regenerate_id();
$_SESSION["user_id"] = $user["user_id"];
header("Location: {$g_httpHomePath}");
exit;
}
}
$is_invalid = true;
}
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js" defer></script>
<script src="validation.js" defer></script>
</head>
<body>
<div class="login_panel">
<?php if (isset($is_invalid) && $is_invalid): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastLogin">
<div class="nak-toast__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</div>
<span class="nak-toast__text">Ungültiger Login!</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastLogin').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php if (isset($_GET["message"]) && $_GET["message"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastPw">
<div class="nak-toast__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
</div>
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastPw').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link auth-logo">
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
<div class="auth-logo__text">
<div class="auth-logo__name">Neuapostolische Kirche</div>
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Anmelden</h2>
<form method="post">
<div class="flexcolumn_center">
<input class="login_field" placeholder="E-Mailadresse" type="email" name="email" id="email"
value="<?= htmlspecialchars($_POST["email"] ?? "") ?>">
<input class="login_field" placeholder="Passwort" type="password" name="password" id="password">
<input class="login_button" type="submit" name="submit" value="Anmelden">
</div>
</form>
<div class="small_text" style="text-align:center;margin-top:24px;color:var(--gray-600);">
Sie haben noch keinen Account für das Intranet?<br>
<a href="signup.php">Registrieren</a><br>
<br>
Passwort vergessen?<br>
<a href="reset-password.php">Passwort zurücksetzen</a><br>
<br>
Probleme bei der Anmeldung?<br>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,13 @@
<?php /* Name: loginout.php
Beschreibung: Logout-Seite */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
session_destroy();
header("Location: {$g_httpLoginPath}");
exit();
@@ -0,0 +1,223 @@
<?php /* Name: reset-password-process.php
Beschreibung: Verarbeitung des Passwort-Resets (3 Schritte)
- Schritt 1: OTP generieren und per SMS + Mail senden
- Schritt 2: OTP verifizieren → Reset-Token erzeugen
- Schritt 3: Neues Passwort setzen mit Reset-Token */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// PostgreSQL-Zeitzone auf PHP-Zeitzone setzen
$pdo->exec("SET timezone = 'Europe/Berlin'");
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: reset-password.php");
exit;
}
$action = $_POST["action"] ?? '';
// =============================================
// SCHRITT 1: OTP anfordern
// =============================================
if ($action === "request_otp") {
$email = trim($_POST["email"] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
header("Location: reset-password.php?error=1");
exit;
}
// Nutzer suchen (muss registriert sein = user_login existiert)
$stmt = $pdo->prepare("SELECT ugi.user_id, ugi.phone, ugi.first_name FROM user_general_information ugi INNER JOIN user_login ul ON ugi.user_id = ul.user_id WHERE ugi.mail = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if (!$user) {
header("Location: reset-password.php?error=1");
exit;
}
if (empty($user['phone'])) {
header("Location: reset-password.php?error=2");
exit;
}
// OTP generieren (6-stellig)
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// Ablaufzeit als UNIX-Timestamp speichern (vermeidet Timezone-Probleme)
$expires_timestamp = time() + 600; // 10 Minuten
$expires_at = date('Y-m-d H:i:s', $expires_timestamp);
// Alte OTPs für diesen Nutzer invalidieren
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0");
$stmt->execute([$user['user_id']]);
// Neues OTP speichern
$stmt = $pdo->prepare("INSERT INTO password_reset_otp (user_id, otp_code, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$user['user_id'], $otp, $expires_at]);
// SMS senden (wie im Seelsorgeplaner)
$phone_full = "+49" . $user['phone'];
$messageText = "Intranet NAK Reinickendorf\n\nIhr Bestaetigungscode: {$otp}\n\nDer Code ist 10 Minuten gueltig.";
$smsData = array(
"phone" => $phone_full,
"message" => $messageText
);
$options = array(
'http' => array(
'header' => "Content-Type: application/json; charset=utf-8",
'method' => 'POST',
'content' => json_encode($smsData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'timeout' => 10,
),
);
$context = stream_context_create($options);
$smsResult = @file_get_contents(rtrim(intranet_env('SMS_GATEWAY_URL'), '/') . '/send_sms', false, $context);
if ($smsResult === FALSE) {
header("Location: reset-password.php?error=4");
exit;
}
// OTP zusätzlich per Mail senden
$greeting = !empty($user['first_name']) ? "Hallo {$user['first_name']}" : "Hallo";
$mailBetreff = "Ihr Bestätigungscode";
$mailNachricht = "{$greeting},\n\n";
$mailNachricht .= "Sie haben das Zurücksetzen Ihres Passworts angefordert.\n\n";
$mailNachricht .= "Ihr Bestätigungscode lautet: {$otp}\n\n";
$mailNachricht .= "Der Code ist 10 Minuten gültig.\n\n";
$mailNachricht .= "Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.";
send_intranet_mail($email, $mailBetreff, $mailNachricht);
// Telefonnummer maskieren für Anzeige (nur letzte 3 Ziffern zeigen)
$phone_masked = str_repeat('*', max(0, strlen($user['phone']) - 3)) . substr($user['phone'], -3);
header("Location: reset-password.php?step=verify&uid={$user['user_id']}&phone=" . urlencode($phone_masked));
exit;
}
// =============================================
// SCHRITT 2: OTP verifizieren → Reset-Token erzeugen
// =============================================
if ($action === "verify_otp") {
$user_id = (int)($_POST["user_id"] ?? 0);
$otp_code = trim($_POST["otp_code"] ?? '');
if ($user_id === 0 || strlen($otp_code) !== 6) {
header("Location: reset-password.php?error=3");
exit;
}
// OTP prüfen – Vergleich mit PHP-Zeit statt DB NOW()
$now = date('Y-m-d H:i:s');
$stmt = $pdo->prepare("SELECT id FROM password_reset_otp WHERE user_id = ? AND otp_code = ? AND used = 0 AND expires_at > ?");
$stmt->execute([$user_id, $otp_code, $now]);
$otpRecord = $stmt->fetch();
if (!$otpRecord) {
header("Location: reset-password.php?step=verify&uid={$user_id}&error=3");
exit;
}
// OTP als verwendet markieren
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE id = ?");
$stmt->execute([$otpRecord['id']]);
// Einmal-Token für Passwort-Änderung generieren (in Session speichern)
$reset_token = bin2hex(random_bytes(32));
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$_SESSION['reset_token'] = $reset_token;
$_SESSION['reset_user_id'] = $user_id;
$_SESSION['reset_token_expires'] = time() + 600; // 10 Minuten
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token));
exit;
}
// =============================================
// SCHRITT 3: Neues Passwort setzen (nach OTP-Verifizierung)
// =============================================
if ($action === "set_password") {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$reset_token = $_POST["reset_token"] ?? '';
$new_password = $_POST["new_password"] ?? '';
$new_password_confirm = $_POST["new_password_confirm"] ?? '';
// Token validieren
if (empty($reset_token)
|| !isset($_SESSION['reset_token'])
|| $reset_token !== $_SESSION['reset_token']
|| !isset($_SESSION['reset_token_expires'])
|| $_SESSION['reset_token_expires'] < time()) {
// Session-Daten aufräumen
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
header("Location: reset-password.php?error=7");
exit;
}
$user_id = (int)$_SESSION['reset_user_id'];
// Passwort-Validierung
if (strlen($new_password) < 8 || !preg_match("/[a-z]/i", $new_password) || !preg_match("/[0-9]/", $new_password)) {
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=5");
exit;
}
if ($new_password !== $new_password_confirm) {
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=6");
exit;
}
// Neues Passwort setzen
$password_hash = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE user_login SET password_hash = ? WHERE user_id = ?");
$stmt->execute([$password_hash, $user_id]);
// User-Daten laden für Mail und Vermerk
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmt->execute([$user_id]);
$resetUserData = $stmt->fetch();
if ($resetUserData) {
$loginUrl = "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/authorization/login.php";
$greeting = !empty($resetUserData['first_name']) ? "Hallo {$resetUserData['first_name']}" : "Hallo";
$mailBetreff = "Passwort erfolgreich geändert";
$mailNachricht = "{$greeting},\n\n";
$mailNachricht .= "Ihr Passwort für das Intranet der NAK Reinickendorf wurde erfolgreich geändert.\n\n";
$mailNachricht .= "Sie können sich ab sofort mit Ihrem neuen Passwort anmelden.";
send_intranet_mail($resetUserData['mail'], $mailBetreff, $mailNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt anmelden", $loginUrl);
// Vermerk in erledigte Tasks
$userName = trim(($resetUserData['first_name'] ?? '') . ' ' . ($resetUserData['last_name'] ?? ''));
if (empty($userName)) $userName = $resetUserData['mail'];
$taskSubject = "Passwort selbst zurückgesetzt: {$userName}";
$taskMessage = "{$userName} ({$resetUserData['mail']}) hat das eigene Passwort erfolgreich zurückgesetzt.";
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$stmt->execute([$userName, $resetUserData['mail'], $taskSubject, $taskMessage]);
}
// Session-Daten aufräumen
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
header("Location: login.php?message=1");
exit;
}
header("Location: reset-password.php");
exit;
?>
@@ -0,0 +1,159 @@
<?php /* Name: reset-password.php
Beschreibung: Passwort-Reset per SMS OTP (3 Schritte)
- Schritt 1: E-Mail eingeben → OTP per SMS
- Schritt 2: OTP-Code verifizieren
- Schritt 3: Neues Passwort setzen */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
$step = $_GET["step"] ?? "email";
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.login_button { position: relative; }
.login_button.is-loading {
color: transparent !important;
pointer-events: none;
}
.login_button.is-loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 22px;
margin: -11px 0 0 -11px;
border: 3px solid rgba(255,255,255,0.35);
border-top-color: #FFFFFF;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="login_panel">
<?php if (isset($_GET["error"])): ?>
<?php
$errorMessages = [
1 => "E-Mail-Adresse nicht gefunden oder nicht registriert.",
2 => "Keine Telefonnummer hinterlegt. Bitte wenden Sie sich an den Administrator.",
3 => "Ungültiger oder abgelaufener Code.",
4 => "SMS konnte nicht gesendet werden. Bitte versuchen Sie es erneut.",
5 => "Passwort muss mindestens 8 Zeichen, einen Buchstaben und eine Zahl enthalten.",
6 => "Passwörter stimmen nicht überein.",
7 => "Sitzung abgelaufen. Bitte starten Sie den Vorgang erneut."
];
$errorNum = (int)$_GET["error"];
$errorText = $errorMessages[$errorNum] ?? "Unbekannter Fehler.";
?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text"><?= htmlspecialchars($errorText) ?></span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link auth-logo">
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
<div class="auth-logo__text">
<div class="auth-logo__name">Neuapostolische Kirche</div>
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Passwort zurücksetzen</h2>
<?php if ($step === "email"): ?>
<!-- SCHRITT 1: E-Mail eingeben -->
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Geben Sie Ihre E-Mail-Adresse ein. Ein Bestätigungscode wird an Ihre hinterlegte Telefonnummer und per Mail gesendet.</p>
<form action="reset-password-process.php" method="post" id="otpRequestForm">
<input type="hidden" name="action" value="request_otp">
<label for="email">E-Mailadresse</label>
<div class="item">
<input class="login_field" type="email" id="email" name="email" required>
</div>
<button class="login_button" type="submit" id="requestBtn">Code anfordern</button>
</form>
<script>
document.getElementById('otpRequestForm').addEventListener('submit', function() {
var btn = document.getElementById('requestBtn');
btn.classList.add('is-loading');
btn.disabled = true;
});
</script>
<?php elseif ($step === "verify"): ?>
<!-- SCHRITT 2: Nur OTP-Code eingeben -->
<?php $phone_masked = htmlspecialchars($_GET["phone"] ?? "***"); ?>
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ein 6-stelliger Code wurde an die Nummer +49 <?= $phone_masked ?> und an Ihre E-Mail-Adresse gesendet.</p>
<form action="reset-password-process.php" method="post">
<input type="hidden" name="action" value="verify_otp">
<input type="hidden" name="user_id" value="<?= (int)($_GET["uid"] ?? 0) ?>">
<label for="otp_code">Bestätigungscode</label>
<div class="item">
<input class="login_field" type="text" id="otp_code" name="otp_code" maxlength="6" inputmode="numeric" pattern="[0-9]{6}" placeholder="000000" required autocomplete="one-time-code" style="text-align:center;font-size:1.4em;letter-spacing:8px;">
</div>
<input class="login_button" type="submit" value="Code bestätigen">
</form>
<?php elseif ($step === "newpassword"): ?>
<!-- SCHRITT 3: Neues Passwort setzen (nur nach verifiziertem OTP) -->
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ihr Code wurde bestätigt. Geben Sie nun Ihr neues Passwort ein.</p>
<form action="reset-password-process.php" method="post">
<input type="hidden" name="action" value="set_password">
<input type="hidden" name="reset_token" value="<?= htmlspecialchars($_GET["token"] ?? '') ?>">
<label for="new_password">Neues Passwort</label>
<div class="item">
<input class="login_field" type="password" id="new_password" name="new_password" required>
</div>
<label for="new_password_confirm">Neues Passwort wiederholen</label>
<div class="item">
<input class="login_field" type="password" id="new_password_confirm" name="new_password_confirm" required>
</div>
<input class="login_button" type="submit" value="Passwort ändern">
</form>
<?php endif; ?>
<div class="small_text" align="center">
<br>
<a href="login.php">Zurück zur Anmeldung</a><br>
<br>
Probleme beim Zurücksetzen?<br>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,97 @@
<?php /* Name: self-signup-process.php
Beschreibung: Verarbeitung der Selbstregistrierung
- Validierung der Eingaben
- Speicherung in pending_registrations
- Mail an Admin (samuel.bias@aol.de)
- Eintrag in admin_messages */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// Validierung
if (empty($_POST["first_name"])) {
die("Vorname ist erforderlich.");
}
if (empty($_POST["last_name"])) {
die("Nachname ist erforderlich.");
}
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
die("Gültige E-Mail-Adresse erforderlich.");
}
if (strlen($_POST["password"]) < 8) {
die("Passwort muss mindestens 8 Zeichen lang sein.");
}
if (!preg_match("/[a-z]/i", $_POST["password"])) {
die("Passwort muss mindestens einen Buchstaben enthalten.");
}
if (!preg_match("/[0-9]/", $_POST["password"])) {
die("Passwort muss mindestens eine Zahl enthalten.");
}
if ($_POST["password"] !== $_POST["password_confirmation"]) {
die("Passwörter stimmen nicht überein.");
}
$email = trim($_POST["email"]);
$first_name = trim($_POST["first_name"]);
$last_name = trim($_POST["last_name"]);
// Prüfen ob E-Mail bereits existiert (in user_general_information oder pending_registrations)
$checkSql = "SELECT COUNT(*) FROM user_general_information WHERE mail = ?";
$stmt = $pdo->prepare($checkSql);
$stmt->execute([$email]);
$count1 = $stmt->fetchColumn();
$checkSql2 = "SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'";
$stmt2 = $pdo->prepare($checkSql2);
$stmt2->execute([$email]);
$count2 = $stmt2->fetchColumn();
if ($count1 > 0 || $count2 > 0) {
header("Location: self-signup.php?error=2");
exit;
}
// Passwort hashen und Token generieren
$password_hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
$token = bin2hex(random_bytes(32));
// In pending_registrations speichern
$insertSql = "INSERT INTO pending_registrations (first_name, last_name, email, password_hash, token) VALUES (?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($insertSql);
if (!$stmt) {
header("Location: self-signup.php?error=1");
exit;
}
if (!$stmt->execute([$first_name, $last_name, $email, $password_hash, $token])) {
header("Location: self-signup.php?error=1");
exit;
}
$pending_id = $pdo->lastInsertId();
// Admin-Nachricht in Inbox speichern
$subject = "Neue Registrierungsanfrage: {$first_name} {$last_name}";
$message_text = "Neue Selbstregistrierung:\n\nName: {$first_name} {$last_name}\nE-Mail: {$email}\n\nBitte im Adminpanel freischalten.";
$data_json = json_encode(['pending_id' => $pending_id, 'token' => $token]);
$inboxSql = "INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, data_json) VALUES ('registration', ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($inboxSql);
$sender_name = "{$first_name} {$last_name}";
$stmt->execute([$sender_name, $email, $subject, $message_text, $data_json]);
// Mail an Admin-Team (Admin + Technik)
$admin_email = ADMIN_EMAIL;
$mail_betreff = "Intranet: Neue Registrierungsanfrage von {$first_name} {$last_name}";
$mail_nachricht = "Hallo Admin,\n\n";
$mail_nachricht .= "Es liegt eine neue Registrierungsanfrage vor:\n\n";
$mail_nachricht .= "Name: {$first_name} {$last_name}\n";
$mail_nachricht .= "E-Mail: {$email}\n\n";
$mail_nachricht .= "Bitte melden Sie sich im Adminpanel an, um die Anfrage zu bearbeiten.\n\n";
$mail_nachricht .= "-- Intranet NAK Reinickendorf";
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht);
// Erfolg
header("Location: self-signup.php?success=1");
exit;
?>
@@ -0,0 +1,101 @@
<?php /* Name: self-signup.php
Beschreibung: Selbstregistrierung - Nutzer kann sich eigenständig registrieren.
Admin erhält Mail + Inbox-Nachricht zur Freischaltung. */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<script src="just-validate.js" defer></script>
<script src="validation.js" defer></script>
</head>
<body>
<div class="login_panel">
<?php if (isset($_GET["error"])): ?>
<?php if ($_GET["error"] == 1): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php elseif ($_GET["error"] == 2): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Diese E-Mailadresse ist bereits registriert oder es liegt bereits eine Anfrage vor.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php endif; ?>
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
<span class="nak-toast__text">Ihre Registrierungsanfrage wurde gesendet. Sie erhalten eine Benachrichtigung, sobald Ihr Konto freigeschaltet wurde.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link" style="display:flex;align-items:flex-end;gap:12px;text-decoration:none;margin-bottom:24px;">
<img src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo" style="width:52px;height:52px;flex-shrink:0;">
<div style="display:flex;flex-direction:column;justify-content:flex-end;line-height:1.2;">
<div style="font-family:var(--font-primary);font-weight:500;font-size:0.875rem;color:var(--gray-900);white-space:nowrap;">Neuapostolische Kirche</div>
<div style="font-family:var(--font-primary);font-weight:300;font-size:0.75rem;color:var(--gray-600);white-space:nowrap;">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Registrierung beantragen</h2>
<h3 class="no_margins" align="center">Ihr Antrag wird von einem Administrator geprüft und freigeschaltet.</h3>
<form action="self-signup-process.php" method="post" id="signup" novalidate>
<label for="first_name"><br>Vorname</label>
<div class="item">
<input class="login_field" type="text" id="first_name" name="first_name" required>
</div>
<label for="last_name">Nachname</label>
<div class="item">
<input class="login_field" type="text" id="last_name" name="last_name" required>
</div>
<label for="email">E-Mailadresse</label>
<div class="item">
<input class="login_field" type="email" id="email" name="email" required>
</div>
<label for="password">Passwort</label>
<div class="item">
<input class="login_field" type="password" id="password" name="password" required>
</div>
<label for="password_confirmation">Passwort wiederholen</label>
<div class="item">
<input class="login_field" type="password" id="password_confirmation" name="password_confirmation" required>
</div>
<input class="login_button" type="submit" value="Registrierung beantragen">
</form>
<div class="small_text" align="center">
Sie haben bereits einen Account?
<br>
<a href="login.php">Anmelden</a>
<br><br>
Sie wurden bereits vom Admin vorregistriert?
<br>
<a href="signup.php">Direkt registrieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,118 @@
<?php /* Name: signup-process.php
Beschreibung: Vereinte Registrierungs-Verarbeitung
- Prüft ob Nutzer vorregistriert ist → direkte Registrierung
- Wenn nicht → automatisch Antrag in pending_registrations + Mail an Admin */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// === Validierung ===
if (empty($_POST["first_name"])) {
die("Vorname ist erforderlich.");
}
if (empty($_POST["last_name"])) {
die("Nachname ist erforderlich.");
}
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
die("Gültige E-Mail-Adresse erforderlich.");
}
if (empty($_POST["phone"])) {
die("Telefonnummer ist erforderlich.");
}
if (strlen($_POST["password"]) < 8) {
die("Passwort muss mindestens 8 Zeichen lang sein.");
}
if (!preg_match("/[a-z]/i", $_POST["password"])) {
die("Passwort muss mindestens einen Buchstaben enthalten.");
}
if (!preg_match("/[0-9]/", $_POST["password"])) {
die("Passwort muss mindestens eine Zahl enthalten.");
}
if ($_POST["password"] !== $_POST["password_confirmation"]) {
die("Passwörter stimmen nicht überein.");
}
$email = trim($_POST["email"]);
$first_name = trim($_POST["first_name"]);
$last_name = trim($_POST["last_name"]);
$phone = preg_replace('/[^0-9]/', '', trim($_POST["phone"]));
$password_hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
// === Prüfung 1: Ist der Nutzer vom Admin vorregistriert? ===
$stmt = $pdo->prepare("SELECT user_id FROM user_general_information WHERE mail = ?");
$stmt->execute([$email]);
$user_id = $stmt->fetchColumn();
if ($user_id) {
// Nutzer ist vorregistriert – prüfen ob bereits Login existiert
$stmt = $pdo->prepare("SELECT COUNT(*) FROM user_login WHERE user_id = ?");
$stmt->execute([$user_id]);
$loginExists = $stmt->fetchColumn();
if ($loginExists > 0) {
// Bereits vollständig registriert
header("Location: signup.php?error=2");
exit;
}
// Direkte Registrierung durchführen
$stmt = $pdo->prepare("INSERT INTO user_login (user_id, password_hash) VALUES (?, ?)");
if ($stmt->execute([$user_id, $password_hash])) {
// Namen und Telefonnummer aktualisieren
$stmt = $pdo->prepare("UPDATE user_general_information SET first_name=?, last_name=?, phone=? WHERE user_id=?");
$stmt->execute([$first_name, $last_name, $phone, $user_id]);
header("Location: login.php");
exit;
} else {
header("Location: signup.php?error=1");
exit;
}
}
// === Prüfung 2: Liegt bereits ein Antrag vor? ===
$stmt = $pdo->prepare("SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'");
$stmt->execute([$email]);
$pendingCount = $stmt->fetchColumn();
if ($pendingCount > 0) {
header("Location: signup.php?error=2");
exit;
}
// === Antrag stellen (Nutzer nicht vorregistriert) ===
$token = bin2hex(random_bytes(32));
$stmt = $pdo->prepare("INSERT INTO pending_registrations (first_name, last_name, email, phone, password_hash, token) VALUES (?, ?, ?, ?, ?, ?)");
if (!$stmt->execute([$first_name, $last_name, $email, $phone, $password_hash, $token])) {
header("Location: signup.php?error=1");
exit;
}
$pending_id = $pdo->lastInsertId();
// Admin-Nachricht in Inbox speichern
$subject = "Neue Registrierungsanfrage: {$first_name} {$last_name}";
$message_text = "Neue Registrierungsanfrage:\n\nName: {$first_name} {$last_name}\nE-Mail: {$email}\n\nBitte im Adminpanel freischalten.";
$data_json = json_encode(['pending_id' => $pending_id, 'token' => $token]);
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, data_json) VALUES ('registration', ?, ?, ?, ?, ?)");
$sender_name = "{$first_name} {$last_name}";
$stmt->execute([$sender_name, $email, $subject, $message_text, $data_json]);
// Mail an Admin-Team (Admin + Technik)
$mail_betreff = "Neue Registrierungsanfrage von {$first_name} {$last_name}";
$mail_nachricht = "Es liegt eine neue Registrierungsanfrage vor:\n\n";
$mail_nachricht .= "Name: {$first_name} {$last_name}\n";
$mail_nachricht .= "E-Mail: {$email}\n";
$mail_nachricht .= "Telefon: {$phone}\n\n";
$mail_nachricht .= "Bitte melden Sie sich im Adminpanel an, um die Anfrage zu bearbeiten.";
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht, "Im Adminpanel bearbeiten", "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/adminpanel/admin_inbox.php");
// Erfolg – gleiche Meldung wie bei direkter Registrierung (Nutzer merkt keinen Unterschied)
header("Location: signup.php?success=1");
exit;
?>
@@ -0,0 +1,111 @@
<?php /* Name: signup.php
Beschreibung: Registrierungsformular (vereint)
- Wenn Nutzer vorregistriert: direkte Registrierung
- Wenn nicht: Antrag wird automatisch gestellt
- Verwendet signup-process.php */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<!-- Quelle für just-validate.js: https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js -->
<script src="just-validate.js" defer></script>
<script src="validation.js" defer></script>
</head>
<body>
<div class="login_panel">
<?php if (isset($_GET["error"])): ?>
<?php if ($_GET["error"] == 1): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php elseif ($_GET["error"] == 2): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Diese E-Mailadresse ist bereits registriert oder es liegt bereits eine Anfrage vor.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php endif; ?>
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
<span class="nak-toast__text">Ihre Registrierung wurde beantragt. Sie erhalten eine Benachrichtigung, sobald Ihr Konto freigeschaltet wurde.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link auth-logo">
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
<div class="auth-logo__text">
<div class="auth-logo__name">Neuapostolische Kirche</div>
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Registrieren</h2>
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Erstellen Sie Ihren persönlichen Zugang zum Intranet.</p>
<form action="signup-process.php" method="post" id="signup" novalidate>
<label for="first_name">Vorname</label>
<div class="item">
<input class="login_field" type="text" id="first_name" name="first_name">
</div>
<label for="last_name">Nachname</label>
<div class="item">
<input class="login_field" type="text" id="last_name" name="last_name">
</div>
<label for="email">E-Mailadresse</label>
<div class="item">
<input class="login_field" type="email" id="email" name="email">
</div>
<label for="phone">Telefonnummer</label>
<div class="item">
<div style="display:flex;align-items:stretch;width:100%;max-width:100%;margin-bottom:10px;box-sizing:border-box;">
<div class="input-prefix" style="border-radius:0;border:1px solid var(--gray-300);border-right:none;margin:0;padding:14px 12px;font-weight:500;box-shadow:none;width:auto;background:var(--gray-200);color:var(--gray-700);display:flex;align-items:center;flex-shrink:0;">+49</div>
<input class="login_field" type="tel" id="phone" name="phone" style="border-radius:0;border-left:0;margin:0;flex:1;width:auto;min-width:0;">
</div>
</div>
<label for="password">Passwort</label>
<div class="item">
<input class="login_field" type="password" id="password" name="password">
</div>
<label for="password_confirmation">Passwort wiederholen</label>
<div class="item">
<input class="login_field" type="password" id="password_confirmation" name="password_confirmation">
</div>
<input class="login_button" type="submit" value="Registrieren">
</form>
<div class="small_text" align="center">
Sie haben bereits einen Account?
<br>
<a href="login.php">Anmelden</a><br>
<br>
Probleme bei der Registrierung?<br>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,40 @@
<?php /* Name: validate-email.php
Beschreibung: Validierung der E-Mailadresse
- Wird innerhalb von validation.js aufgerufen
- Prüft ob E-Mail bereits vollständig registriert oder
ein offener Antrag vorliegt */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
header("Content-Type: application/json");
if (!isset($_GET["email"]) || empty(trim($_GET["email"]))) {
echo json_encode(["available" => false]);
exit;
}
$email = trim($_GET["email"]);
$is_available = true;
// Prüfung 1: Gibt es einen vollständigen Login für diese E-Mail?
$stmt = $pdo->prepare("SELECT u.user_id FROM user_general_information u INNER JOIN user_login l ON u.user_id = l.user_id WHERE u.mail = ?");
$stmt->execute([$email]);
if ($stmt->fetch()) {
$is_available = false;
}
// Prüfung 2: Liegt bereits ein offener Antrag vor?
if ($is_available) {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'");
$stmt->execute([$email]);
$pendingCount = $stmt->fetchColumn();
if ($pendingCount > 0) {
$is_available = false;
}
}
echo json_encode(["available" => $is_available]);
exit;
?>
@@ -0,0 +1,74 @@
const validation = new JustValidate("#signup");
validation
.addField("#first_name", [
{
rule: "required",
errorMessage: "Pflichtfeld"
}
])
.addField("#last_name", [
{
rule: "required",
errorMessage: "Pflichtfeld"
}
])
.addField("#email", [
{
rule: "required",
errorMessage: "Pflichtfeld"
},
{
rule: "email",
errorMessage: "Ungültig"
},
{
validator: (value) => () => {
return fetch("validate-email.php?email=" + encodeURIComponent(value))
.then(function(response) {
return response.json();
})
.then(function(json) {
return json.available;
});
},
errorMessage: "Bereits registriert"
}
])
.addField("#phone", [
{
rule: "required",
errorMessage: "Pflichtfeld"
},
{
rule: "minLength",
value: 6,
errorMessage: "Ungültige Telefonnummer"
},
{
validator: (value) => {
return /^[1-9][0-9\s\-\/()]{4,}$/.test(value);
},
errorMessage: "Ohne führende 0, nur Ziffern"
}
])
.addField("#password", [
{
rule: "required",
errorMessage: "Pflichtfeld"
},
{
rule: "password",
errorMessage: "Zu leicht"
}
])
.addField("#password_confirmation", [
{
validator: (value, fields) => {
return value === fields["#password"].elem.value;
},
errorMessage: "Passwörter müssen übereinstimmen"
}
])
.onSuccess((event) => {
document.getElementById("signup").submit();
});