325 lines
16 KiB
PHP
325 lines
16 KiB
PHP
<?php
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/parameters.php';
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/functions.php';
|
|
require_once __DIR__ . '/util/authorization_helpers.php';
|
|
|
|
authorization_start_session();
|
|
authorization_send_security_headers();
|
|
|
|
if (isset($_SESSION['user_id'])) {
|
|
authorization_redirect($g_httpHomePath, 302);
|
|
}
|
|
|
|
$allowedSteps = ['email', 'verify', 'newpassword'];
|
|
$step = (string) ($_GET['step'] ?? 'email');
|
|
if (!in_array($step, $allowedSteps, true)) {
|
|
$step = 'email';
|
|
}
|
|
|
|
$error = null;
|
|
$errorCode = (int) ($_GET['error'] ?? 0);
|
|
$errorMessages = [
|
|
1 => 'Bitte geben Sie eine gültige E-Mail-Adresse ein.',
|
|
3 => 'Ungültiger oder abgelaufener Bestätigungscode.',
|
|
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.',
|
|
];
|
|
if (isset($errorMessages[$errorCode])) {
|
|
$error = $errorMessages[$errorCode];
|
|
}
|
|
|
|
if ($step === 'verify' && !authorization_reset_request_is_active()) {
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7', 302);
|
|
}
|
|
if ($step === 'newpassword' && !authorization_reset_is_authorized()) {
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7', 302);
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = (string) ($_POST['action'] ?? '');
|
|
|
|
if (!authorization_csrf_is_valid($_POST['csrf_token'] ?? null)) {
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7');
|
|
}
|
|
|
|
$pdo = require $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/database.php';
|
|
$pdo->exec("SET timezone = 'Europe/Berlin'");
|
|
|
|
if ($action === 'request_otp') {
|
|
$email = trim((string) ($_POST['email'] ?? ''));
|
|
if (strlen($email) > 254 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
authorization_redirect('reset-password.php?error=1');
|
|
}
|
|
|
|
authorization_clear_reset_state();
|
|
$_SESSION['auth_reset_started_at'] = time();
|
|
$_SESSION['auth_reset_attempts'] = 0;
|
|
$_SESSION['auth_reset_user_id'] = 0;
|
|
|
|
$stmt = $pdo->prepare(
|
|
'SELECT ugi.user_id, ugi.phone, ugi.first_name, ugi.mail '
|
|
. 'FROM user_general_information ugi '
|
|
. 'INNER JOIN user_login ul ON ul.user_id = ugi.user_id '
|
|
. 'WHERE LOWER(ugi.mail) = LOWER(?) LIMIT 1'
|
|
);
|
|
$stmt->execute([$email]);
|
|
$resetUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (is_array($resetUser)) {
|
|
$userId = (int) $resetUser['user_id'];
|
|
$otp = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
|
$expiresAt = date('Y-m-d H:i:s', time() + AUTH_RESET_TTL_SECONDS);
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$stmt = $pdo->prepare('UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0');
|
|
$stmt->execute([$userId]);
|
|
$stmt = $pdo->prepare('INSERT INTO password_reset_otp (user_id, otp_code, expires_at) VALUES (?, ?, ?)');
|
|
$stmt->execute([$userId, $otp, $expiresAt]);
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
error_log('authorization reset OTP persistence failed: ' . $e->getMessage());
|
|
authorization_redirect('reset-password.php?step=verify&sent=1');
|
|
}
|
|
|
|
$mailSent = false;
|
|
try {
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/send_mail.php';
|
|
$greeting = !empty($resetUser['first_name']) ? 'Hallo ' . $resetUser['first_name'] : 'Hallo';
|
|
$mailMessage = $greeting . ",\n\n"
|
|
. "Sie haben das Zurücksetzen Ihres Passworts angefordert.\n\n"
|
|
. "Ihr Bestätigungscode lautet: {$otp}\n\n"
|
|
. "Der Code ist 10 Minuten gültig.\n\n"
|
|
. 'Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.';
|
|
$mailSent = send_intranet_mail((string) $resetUser['mail'], 'Ihr Bestätigungscode', $mailMessage);
|
|
} catch (Throwable $e) {
|
|
error_log('authorization reset mail delivery failed: ' . $e->getMessage());
|
|
}
|
|
$smsSent = authorization_send_reset_sms(authorization_normalize_phone((string) ($resetUser['phone'] ?? '')), $otp);
|
|
|
|
if ($mailSent || $smsSent) {
|
|
$_SESSION['auth_reset_user_id'] = $userId;
|
|
} else {
|
|
error_log('authorization reset delivery failed for user_id=' . $userId);
|
|
$stmt = $pdo->prepare('UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0');
|
|
$stmt->execute([$userId]);
|
|
}
|
|
}
|
|
|
|
// Immer dieselbe Antwort, damit aus dem Reset-Flow kein Kontostatus abgeleitet werden kann.
|
|
authorization_redirect('reset-password.php?step=verify&sent=1');
|
|
}
|
|
|
|
if ($action === 'verify_otp') {
|
|
if (!authorization_reset_request_is_active()) {
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7');
|
|
}
|
|
|
|
$otp = trim((string) ($_POST['otp_code'] ?? ''));
|
|
$attempts = (int) ($_SESSION['auth_reset_attempts'] ?? 0) + 1;
|
|
$_SESSION['auth_reset_attempts'] = $attempts;
|
|
$userId = (int) ($_SESSION['auth_reset_user_id'] ?? 0);
|
|
|
|
if (!preg_match('/^[0-9]{6}$/', $otp) || $userId <= 0 || $attempts > AUTH_RESET_MAX_ATTEMPTS) {
|
|
if ($attempts >= AUTH_RESET_MAX_ATTEMPTS && $userId > 0) {
|
|
$stmt = $pdo->prepare('UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0');
|
|
$stmt->execute([$userId]);
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7');
|
|
}
|
|
authorization_redirect('reset-password.php?step=verify&error=3');
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$stmt = $pdo->prepare(
|
|
'SELECT id FROM password_reset_otp '
|
|
. 'WHERE user_id = ? AND otp_code = ? AND used = 0 AND expires_at > ? '
|
|
. 'ORDER BY id DESC LIMIT 1 FOR UPDATE'
|
|
);
|
|
$stmt->execute([$userId, $otp, $now]);
|
|
$otpId = (int) ($stmt->fetchColumn() ?: 0);
|
|
|
|
if ($otpId <= 0) {
|
|
$pdo->rollBack();
|
|
authorization_redirect('reset-password.php?step=verify&error=3');
|
|
}
|
|
|
|
$stmt = $pdo->prepare('UPDATE password_reset_otp SET used = 1 WHERE id = ?');
|
|
$stmt->execute([$otpId]);
|
|
$pdo->commit();
|
|
|
|
session_regenerate_id(true);
|
|
$_SESSION['auth_reset_user_id'] = $userId;
|
|
$_SESSION['auth_reset_authorized_until'] = time() + AUTH_RESET_TTL_SECONDS;
|
|
unset($_SESSION['auth_reset_attempts']);
|
|
authorization_redirect('reset-password.php?step=newpassword');
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
error_log('authorization OTP verification failed: ' . $e->getMessage());
|
|
authorization_redirect('reset-password.php?step=verify&error=3');
|
|
}
|
|
}
|
|
|
|
if ($action === 'set_password') {
|
|
if (!authorization_reset_is_authorized()) {
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7');
|
|
}
|
|
|
|
$newPassword = (string) ($_POST['new_password'] ?? '');
|
|
$confirmation = (string) ($_POST['new_password_confirm'] ?? '');
|
|
$passwordError = authorization_password_error($newPassword, $confirmation);
|
|
if ($passwordError !== null) {
|
|
$code = str_contains($passwordError, 'stimmen') ? 6 : 5;
|
|
authorization_redirect('reset-password.php?step=newpassword&error=' . $code);
|
|
}
|
|
|
|
$userId = (int) $_SESSION['auth_reset_user_id'];
|
|
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
|
$resetUser = null;
|
|
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$stmt = $pdo->prepare('UPDATE user_login SET password_hash = ? WHERE user_id = ?');
|
|
$stmt->execute([$passwordHash, $userId]);
|
|
if ($stmt->rowCount() !== 1) {
|
|
throw new RuntimeException('Reset target login no longer exists.');
|
|
}
|
|
|
|
$stmt = $pdo->prepare('UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0');
|
|
$stmt->execute([$userId]);
|
|
|
|
$stmt = $pdo->prepare('SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?');
|
|
$stmt->execute([$userId]);
|
|
$resetUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
if (!is_array($resetUser)) {
|
|
throw new RuntimeException('Reset target user no longer exists.');
|
|
}
|
|
|
|
$userName = trim(($resetUser['first_name'] ?? '') . ' ' . ($resetUser['last_name'] ?? ''));
|
|
if ($userName === '') {
|
|
$userName = (string) $resetUser['mail'];
|
|
}
|
|
$taskSubject = "Passwort selbst zurückgesetzt: {$userName}";
|
|
$taskMessage = "{$userName} ({$resetUser['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, $resetUser['mail'], $taskSubject, $taskMessage]);
|
|
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
error_log('authorization password reset failed: ' . $e->getMessage());
|
|
authorization_clear_reset_state();
|
|
authorization_redirect('reset-password.php?error=7');
|
|
}
|
|
|
|
try {
|
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/general_files/ini/send_mail.php';
|
|
$loginUrl = authorization_public_url('/intranet_repo/projects/authorization/login.php');
|
|
$greeting = !empty($resetUser['first_name']) ? 'Hallo ' . $resetUser['first_name'] : 'Hallo';
|
|
$message = $greeting . ",\n\n"
|
|
. "Ihr Passwort für das Intranet der NAK Reinickendorf wurde erfolgreich geändert.\n\n"
|
|
. 'Sie können sich ab sofort mit Ihrem neuen Passwort anmelden.';
|
|
send_intranet_mail((string) $resetUser['mail'], 'Passwort erfolgreich geändert', $message, null, 'intranet@nak-reinickendorf.de', 'Jetzt anmelden', $loginUrl);
|
|
} catch (Throwable $e) {
|
|
error_log('authorization password reset notification failed: ' . $e->getMessage());
|
|
}
|
|
|
|
authorization_clear_reset_state();
|
|
session_regenerate_id(true);
|
|
authorization_redirect('login.php?message=1');
|
|
}
|
|
|
|
authorization_redirect('reset-password.php');
|
|
}
|
|
|
|
$csrfToken = authorization_csrf_token();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="de" dir="ltr">
|
|
<head>
|
|
<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/projects/_central/php/html_head.php'; ?>
|
|
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/authorization.css">
|
|
<script src="/intranet_repo/projects/_central/scripts/authorization.js" defer></script>
|
|
</head>
|
|
<body>
|
|
<div class="login_panel">
|
|
<?php if ($error !== null): ?>
|
|
<div class="nak-toast nak-toast--error nak-toast--inline" data-auth-toast>
|
|
<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"><?= authorization_html($error) ?></span>
|
|
</div>
|
|
<?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'): ?>
|
|
<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. Wenn ein passendes Konto vorhanden ist, senden wir einen Bestätigungscode an die hinterlegten Kontaktwege.</p>
|
|
<form method="post" id="otpRequestForm">
|
|
<input type="hidden" name="action" value="request_otp">
|
|
<input type="hidden" name="csrf_token" value="<?= authorization_html($csrfToken) ?>">
|
|
<label for="email">E-Mailadresse</label>
|
|
<div class="item"><input class="login_field" type="email" id="email" name="email" maxlength="254" required autocomplete="email"></div>
|
|
<button class="login_button" type="submit" id="requestBtn">Code anfordern</button>
|
|
</form>
|
|
|
|
<?php elseif ($step === 'verify'): ?>
|
|
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);"><?= authorization_html(authorization_masked_reset_message()) ?></p>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="verify_otp">
|
|
<input type="hidden" name="csrf_token" value="<?= authorization_html($csrfToken) ?>">
|
|
<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'): ?>
|
|
<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 method="post">
|
|
<input type="hidden" name="action" value="set_password">
|
|
<input type="hidden" name="csrf_token" value="<?= authorization_html($csrfToken) ?>">
|
|
<label for="new_password">Neues Passwort</label>
|
|
<div class="item"><input class="login_field" type="password" id="new_password" name="new_password" required autocomplete="new-password"></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 autocomplete="new-password"></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>
|
|
</div>
|
|
|
|
<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/intranet_repo/projects/_central/php/footer.php'; ?>
|
|
</body>
|
|
</html>
|