Files
pnpaas-adm/app/register.php
T

176 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/bootstrap.php';
pnpaas_session_start();
if (!empty($_SESSION['pnpaas_admin'])) {
header('Location: dashboard.php');
exit;
}
$csrfToken = pnpaas_csrf_token();
$documents = pnpaas_registration_documents();
$registrationEnabled = true;
foreach ($documents as $document) {
if (($document['url'] ?? '') === '' || filter_var($document['url'], FILTER_VALIDATE_URL) === false || !str_starts_with(strtolower($document['url']), 'https://')) {
$registrationEnabled = false;
break;
}
}
$error = null;
$success = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim((string)($_POST['username'] ?? ''));
$email = strtolower(trim((string)($_POST['email'] ?? '')));
$firstName = trim((string)($_POST['first_name'] ?? ''));
$lastName = trim((string)($_POST['last_name'] ?? ''));
$streetAddress = trim((string)($_POST['street_address'] ?? ''));
$postalCode = trim((string)($_POST['postal_code'] ?? ''));
$city = trim((string)($_POST['city'] ?? ''));
$countryCode = strtoupper(trim((string)($_POST['country_code'] ?? 'AT')));
$password = (string)($_POST['password'] ?? '');
$passwordConfirmation = (string)($_POST['password_confirmation'] ?? '');
if (!$registrationEnabled) {
$error = 'Die Registrierung ist derzeit noch nicht freigeschaltet.';
} elseif (!pnpaas_valid_csrf((string)($_POST['csrf_token'] ?? ''))) {
$error = 'Die Sitzung ist abgelaufen. Bitte laden Sie die Seite neu.';
} elseif (!preg_match('/^[A-Za-z0-9][A-Za-z0-9_.-]{2,79}$/', $username)) {
$error = 'Der Benutzername muss 3 bis 80 Zeichen enthalten und darf nur Buchstaben, Zahlen, Punkt, Bindestrich und Unterstrich enthalten.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'Bitte geben Sie eine gültige E-Mail-Adresse ein.';
} elseif ($firstName === '' || strlen($firstName) > 100 || $lastName === '' || strlen($lastName) > 100) {
$error = 'Bitte geben Sie Vor- und Nachnamen ein.';
} elseif ($streetAddress === '' || strlen($streetAddress) > 180 || $postalCode === '' || strlen($postalCode) > 20 || $city === '' || strlen($city) > 100 || $countryCode !== 'AT') {
$error = 'Bitte geben Sie eine vollständige österreichische Adresse ein.';
} elseif ((string)($_POST['consent_terms'] ?? '') !== '1' || (string)($_POST['consent_privacy'] ?? '') !== '1' || (string)($_POST['consent_withdrawal'] ?? '') !== '1') {
$error = 'Bitte bestätigen Sie die drei verlinkten Rechtstexte.';
} elseif (strlen($password) < 12) {
$error = 'Das Passwort muss mindestens 12 Zeichen lang sein.';
} elseif (!hash_equals($password, $passwordConfirmation)) {
$error = 'Die Passwörter stimmen nicht überein.';
} else {
try {
$db = pnpaas_db();
$db->beginTransaction();
$db->exec('DELETE FROM account_activation_tokens WHERE expires_at < NOW() OR used_at IS NOT NULL');
$insertUser = $db->prepare(
'INSERT INTO users (username, email, first_name, last_name, street_address, postal_code, city, country_code, password_hash, role, status)
VALUES (:username, :email, :first_name, :last_name, :street_address, :postal_code, :city, :country_code, :password_hash, "user", "pending")'
);
$insertUser->execute([
'username' => $username,
'email' => $email,
'first_name' => $firstName,
'last_name' => $lastName,
'street_address' => $streetAddress,
'postal_code' => $postalCode,
'city' => $city,
'country_code' => $countryCode,
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
]);
$userId = (int)$db->lastInsertId();
$insertConsent = $db->prepare(
'INSERT INTO user_consents (user_id, consent_type, document_url, document_version, consented_at, consent_ip)
VALUES (:user_id, :consent_type, :document_url, :document_version, NOW(), :consent_ip)'
);
foreach ($documents as $consentType => $document) {
$insertConsent->execute([
'user_id' => $userId,
'consent_type' => $consentType,
'document_url' => $document['url'],
'document_version' => $document['version'] !== '' ? $document['version'] : null,
'consent_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
]);
}
$token = bin2hex(random_bytes(32));
$insertToken = $db->prepare(
'INSERT INTO account_activation_tokens (user_id, token_hash, expires_at, requested_ip)
VALUES (:user_id, :token_hash, DATE_ADD(NOW(), INTERVAL 24 HOUR), :requested_ip)'
);
$insertToken->execute([
'user_id' => $userId,
'token_hash' => hash('sha256', $token),
'requested_ip' => $_SERVER['REMOTE_ADDR'] ?? null,
]);
$db->commit();
if (!pnpaas_send_account_activation($email, $token)) {
error_log('PnPaaS account activation mail could not be sent.');
$error = 'Das Konto wurde angelegt, aber die Aktivierungs-E-Mail konnte nicht versendet werden. Bitte wenden Sie sich an den Administrator.';
} else {
$success = 'Die Registrierung war erfolgreich. Bitte prüfen Sie Ihr E-Mail-Postfach und aktivieren Sie Ihr Konto über den zugesendeten Link.';
}
} catch (PDOException $exception) {
if (isset($db) && $db->inTransaction()) $db->rollBack();
if ($exception->getCode() === '23000') {
$error = 'Benutzername oder E-Mail-Adresse wird bereits verwendet.';
} else {
error_log('PnPaaS registration database error: ' . $exception->getMessage());
$error = 'Die Registrierung ist derzeit nicht möglich. Bitte versuchen Sie es später erneut.';
}
} catch (Throwable $exception) {
if (isset($db) && $db->inTransaction()) $db->rollBack();
error_log('PnPaaS registration error: ' . $exception->getMessage());
$error = 'Die Registrierung ist derzeit nicht möglich. Bitte versuchen Sie es später erneut.';
}
}
}
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PnPaaS Registrieren</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600&family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/style.css">
</head>
<body class="auth-page">
<main class="auth-card" aria-labelledby="register-title">
<div class="brand-mark" aria-hidden="true">✦</div>
<p class="eyebrow">PnPaaS</p>
<h1 id="register-title">Konto erstellen</h1>
<p class="intro">Registrieren Sie sich. Anschließend erhalten Sie eine E-Mail zur Aktivierung Ihres Kontos.</p>
<?php if ($error !== null): ?><div class="alert alert-error" role="alert"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
<?php if ($success !== null): ?><div class="alert alert-success" role="status"><?= htmlspecialchars($success, ENT_QUOTES, 'UTF-8') ?></div><?php endif; ?>
<?php if (!$registrationEnabled): ?><div class="alert alert-error" role="status">Die Registrierung wird freigeschaltet, sobald die drei externen Rechtstexte hinterlegt sind.</div><?php endif; ?>
<?php if ($success === null): ?>
<form method="post" action="register.php" class="login-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
<fieldset <?= !$registrationEnabled ? 'disabled' : '' ?> style="border:0; padding:0; margin:0; display:grid; gap:.65rem;">
<label for="first_name">Vorname</label>
<input id="first_name" name="first_name" type="text" autocomplete="given-name" maxlength="100" required value="<?= htmlspecialchars((string)($_POST['first_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="last_name">Nachname</label>
<input id="last_name" name="last_name" type="text" autocomplete="family-name" maxlength="100" required value="<?= htmlspecialchars((string)($_POST['last_name'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="street_address">Straße und Hausnummer</label>
<input id="street_address" name="street_address" type="text" autocomplete="street-address" maxlength="180" required value="<?= htmlspecialchars((string)($_POST['street_address'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="postal_code">Postleitzahl</label>
<input id="postal_code" name="postal_code" type="text" autocomplete="postal-code" maxlength="20" required value="<?= htmlspecialchars((string)($_POST['postal_code'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="city">Ort</label>
<input id="city" name="city" type="text" autocomplete="address-level2" maxlength="100" required value="<?= htmlspecialchars((string)($_POST['city'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="country_code">Land</label>
<select id="country_code" name="country_code" autocomplete="country" required><option value="AT" selected>Österreich</option></select>
<label for="username">Benutzername</label>
<input id="username" name="username" type="text" autocomplete="username" minlength="3" maxlength="80" required value="<?= htmlspecialchars((string)($_POST['username'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="email">E-Mail-Adresse</label>
<input id="email" name="email" type="email" autocomplete="email" maxlength="254" required value="<?= htmlspecialchars((string)($_POST['email'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
<label for="password">Passwort</label>
<input id="password" name="password" type="password" autocomplete="new-password" minlength="12" required>
<label for="password_confirmation">Passwort wiederholen</label>
<input id="password_confirmation" name="password_confirmation" type="password" autocomplete="new-password" minlength="12" required>
<?php foreach ($documents as $consentType => $document): ?><label class="consent-line"><input type="checkbox" name="consent_<?= htmlspecialchars($consentType, ENT_QUOTES, 'UTF-8') ?>" value="1" required> <?= $consentType === 'terms' ? 'Ich akzeptiere die' : 'Ich habe die' ?> <a href="<?= htmlspecialchars($document['url'] !== '' ? $document['url'] : '#', ENT_QUOTES, 'UTF-8') ?>" target="_blank" rel="noopener noreferrer"><?= htmlspecialchars($document['title'], ENT_QUOTES, 'UTF-8') ?></a> <?= $consentType === 'terms' ? '.' : 'gelesen.' ?></label><?php endforeach; ?>
<button type="submit">Registrieren</button>
</fieldset>
</form>
<?php endif; ?>
<p class="form-footer"><a href="index.php">Zurück zur Anmeldung</a></p>
</main>
</body>
</html>