diff --git a/register.php b/register.php
index 7101fc2..c0d9340 100644
--- a/register.php
+++ b/register.php
@@ -2,6 +2,7 @@
require __DIR__ . '/session.php';
require __DIR__ . '/db.php';
require __DIR__ . '/lib/mailer.php';
+require __DIR__ . '/lib/ticket.php';
start_app_session();
if (isset($_SESSION['username'])) {
@@ -9,7 +10,20 @@ if (isset($_SESSION['username'])) {
exit;
}
-const STARTING_CREDITS = 3;
+const REGISTRATION_WINDOW_SECONDS = 3600;
+const MAX_REGISTRATIONS_PER_IP = 5;
+
+function registrationAttemptCount(PDO $pdo, string $identifier, int $windowSeconds): int
+{
+ $stmt = $pdo->prepare("SELECT COUNT(*) FROM registration_attempts WHERE identifier = :identifier AND created_at > datetime('now', :window)");
+ $stmt->execute(['identifier' => $identifier, 'window' => '-' . $windowSeconds . ' seconds']);
+ return (int) $stmt->fetchColumn();
+}
+
+function recordRegistrationAttempt(PDO $pdo, string $identifier): void
+{
+ $pdo->prepare('INSERT INTO registration_attempts (identifier) VALUES (:identifier)')->execute(['identifier' => $identifier]);
+}
$errors = [];
$username = '';
@@ -23,74 +37,93 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
$pdo = getDbConnection();
+ $pdo->exec('CREATE TABLE IF NOT EXISTS registration_attempts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ identifier TEXT NOT NULL,
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )');
+ $pdo->exec('CREATE INDEX IF NOT EXISTS idx_registration_attempts_identifier ON registration_attempts (identifier)');
- if ($username === '') {
- $errors['username'] = 'Username is required.';
- } elseif (strlen($username) < 3 || strlen($username) > 12) {
- $errors['username'] = 'Username must be between 3 and 12 characters long.';
- } elseif (!preg_match('/^[a-z0-9]+$/', $username)) {
- $errors['username'] = 'Username can only contain lowercase letters (a-z) and numbers (0-9).';
+ $ipIdentifier = 'ip:' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
+
+ if (!csrf_verify($_POST['csrf_token'] ?? null)) {
+ $errors['csrf'] = 'Your session has expired. Please refresh the page and try again.';
+ } elseif (registrationAttemptCount($pdo, $ipIdentifier, REGISTRATION_WINDOW_SECONDS) >= MAX_REGISTRATIONS_PER_IP) {
+ $errors['rate_limit'] = 'Too many accounts have been created from this network. Please try again later.';
} else {
- $stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = :username');
- $stmt->execute(['username' => $username]);
- if ($stmt->fetch()) {
- $errors['username'] = 'That username is not available.';
- }
- }
-
- if ($email === '') {
- $errors['email'] = 'Email is required.';
- } elseif (strlen($email) > 100) {
- $errors['email'] = 'Email cannot exceed 100 characters.';
- } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
- $errors['email'] = 'Please enter a valid email address.';
- } else {
- $stmt = $pdo->prepare('SELECT 1 FROM users WHERE email = :email');
- $stmt->execute(['email' => $email]);
- if ($stmt->fetch()) {
- $errors['email'] = "We couldn't create your account with those details. If you already have an account, try logging in instead.";
- }
- }
-
- if ($password === '') {
- $errors['password'] = 'Password is required.';
- } elseif (strlen($password) < 8) {
- $errors['password'] = 'Password must be at least 8 characters long.';
- } elseif (strlen($password) > 72) {
- $errors['password'] = 'Password cannot exceed 72 characters.';
- }
-
- if (empty($errors)) {
- $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
-
- try {
- $stmt = $pdo->prepare('INSERT INTO users (username, email, password, credits) VALUES (:username, :email, :password, :credits)');
- $stmt->execute([
- 'username' => $username,
- 'email' => $email,
- 'password' => $hashedPassword,
- 'credits' => STARTING_CREDITS,
- ]);
- } catch (PDOException $e) {
- if (str_contains($e->getMessage(), 'UNIQUE constraint failed: users.username')) {
+ if ($username === '') {
+ $errors['username'] = 'Username is required.';
+ } elseif (strlen($username) < 3 || strlen($username) > 12) {
+ $errors['username'] = 'Username must be between 3 and 12 characters long.';
+ } elseif (!preg_match('/^[a-z0-9]+$/', $username)) {
+ $errors['username'] = 'Username can only contain lowercase letters (a-z) and numbers (0-9).';
+ } else {
+ $stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = :username');
+ $stmt->execute(['username' => $username]);
+ if ($stmt->fetch()) {
$errors['username'] = 'That username is not available.';
- } elseif (str_contains($e->getMessage(), 'UNIQUE constraint failed: users.email')) {
- $errors['email'] = "We couldn't create your account with those details. If you already have an account, try logging in instead.";
- } else {
- throw $e;
}
}
+ if ($email === '') {
+ $errors['email'] = 'Email is required.';
+ } elseif (strlen($email) > 100) {
+ $errors['email'] = 'Email cannot exceed 100 characters.';
+ } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ $errors['email'] = 'Please enter a valid email address.';
+ } else {
+ $stmt = $pdo->prepare('SELECT 1 FROM users WHERE email = :email');
+ $stmt->execute(['email' => $email]);
+ if ($stmt->fetch()) {
+ $errors['email'] = "We couldn't create your account with those details. If you already have an account, try logging in instead.";
+ }
+ }
+
+ if ($password === '') {
+ $errors['password'] = 'Password is required.';
+ } elseif (strlen($password) < 8) {
+ $errors['password'] = 'Password must be at least 8 characters long.';
+ } elseif (strlen($password) > 72) {
+ $errors['password'] = 'Password cannot exceed 72 characters.';
+ }
+
if (empty($errors)) {
- start_app_session();
- session_regenerate_id(true);
- $_SESSION['user_id'] = (int) $pdo->lastInsertId();
- $_SESSION['username'] = $username;
+ $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
- sendRegistrationConfirmationEmail($email, $username);
+ try {
+ $stmt = $pdo->prepare('INSERT INTO users (username, email, password, credits) VALUES (:username, :email, :password, 0)');
+ $stmt->execute([
+ 'username' => $username,
+ 'email' => $email,
+ 'password' => $hashedPassword,
+ ]);
+ recordRegistrationAttempt($pdo, $ipIdentifier);
+ } catch (PDOException $e) {
+ if (str_contains($e->getMessage(), 'UNIQUE constraint failed: users.username')) {
+ $errors['username'] = 'That username is not available.';
+ } elseif (str_contains($e->getMessage(), 'UNIQUE constraint failed: users.email')) {
+ $errors['email'] = "We couldn't create your account with those details. If you already have an account, try logging in instead.";
+ } else {
+ throw $e;
+ }
+ }
- header('Location: /');
- exit;
+ if (empty($errors)) {
+ $newUserId = (int) $pdo->lastInsertId();
+
+ start_app_session();
+ session_regenerate_id(true);
+ $_SESSION['user_id'] = $newUserId;
+ $_SESSION['username'] = $username;
+
+ $verifyToken = createTicket($newUserId, 'email_verify', EMAIL_VERIFICATION_LIFETIME_SECONDS);
+ $appUrl = rtrim(getenv('APP_URL') ?: 'https://ourapp.local', '/');
+ $verifyLink = $appUrl . '/verify-email.php?token=' . urlencode($verifyToken);
+ sendEmailVerificationEmail($email, $username, $verifyLink);
+
+ header('Location: /');
+ exit;
+ }
}
}
}
@@ -122,7 +155,14 @@ $pageTitle = 'Register';
Create an account and bring your imagination to life.
+
+
+
+
+
+