1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 if ($USER) go('/'); 4 if (cfg('allow_reg','1') !== '1') $disabled = true; 5 6 $errs = []; 7 8 // IMPORTANT: Only generate a NEW captcha on GET requests (page load). 9 // On POST requests, verify FIRST against the stored session answer, 10 // then generate a new one only if needed for re-display. 11 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { 12 $captcha = captcha_generate(); 13 } else { 14 // On POST: we don't overwrite the session answer yet. 15 // captcha_verify() will read and unset it. 16 $captcha = ['q' => '']; // placeholder, overwritten below if needed 17 } 18 19 if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($disabled)) { 20 if (!csrf_ok()) { 21 $errs[] = 'Invalid request. Please refresh and try again.'; 22 $captcha = captcha_generate(); 23 } else { 24 $name = sanitise(post('username')); 25 $email = sanitise(post('email')); 26 $pass = post('password'); 27 $pass2 = post('password2'); 28 $cap = post('captcha'); 29 30 // Verify captcha FIRST before anything else 31 if (!captcha_verify($cap)) { 32 $errs[] = 'Incorrect answer to the security question. Please try again.'; 33 $captcha = captcha_generate(); // generate fresh question for retry 34 } 35 36 // Only run other validation if captcha passed 37 if (!$errs) { 38 if (strlen($name) < 3 || strlen($name) > 30) $errs[] = 'Username must be 3โ30 characters.'; 39 if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $name)) $errs[] = 'Username: letters, numbers, _ and - only.'; 40 if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errs[] = 'Invalid email address.'; 41 if (strlen($pass) < 8) $errs[] = 'Password must be at least 8 characters.'; 42 if ($pass !== $pass2) $errs[] = 'Passwords do not match.'; 43 } 44 45 if (!$errs) { 46 if (DB::row('SELECT id FROM users WHERE username=? OR email=?', [$name, $email])) { 47 $errs[] = 'Username or email is already taken.'; 48 $captcha = captcha_generate(); // fresh question after failed attempt 49 } else { 50 $id = DB::insert( 51 'INSERT INTO users (username, email, password) VALUES (?, ?, ?)', 52 [$name, $email, password_hash($pass, PASSWORD_BCRYPT, ['cost' => 12])] 53 ); 54 addon_hook('after_user_registered', ['user_id'=>$id,'username'=>$name,'email'=>$email]); 55 login_user($id); 56 go('/'); 57 } 58 } 59 60 // If we have errors but captcha already passed (errs from other fields), 61 // generate a fresh captcha for the re-shown form 62 if ($errs && empty(array_filter($errs, fn($e) => str_contains($e, 'security')))) { 63 if (!isset($captcha['q']) || $captcha['q'] === '') { 64 $captcha = captcha_generate(); 65 } 66 } 67 } 68 } 69 ?><!DOCTYPE html> 70 <html lang="en"> 71 <head> 72 <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> 73 <title>Sign Up โ <?= e(cfg('site_name','Nexus Forum')) ?></title> 74 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> 75 <link rel="stylesheet" href="<?= asset('css/main.css') ?>"> 76 </head> 77 <body class="auth-body"> 78 <div class="auth-page"> 79 <div class="auth-card"> 80 <a href="<?= u('/') ?>" class="auth-logo"> 81 <div class="logo-mark"><?= e(substr(cfg('site_name','N'),0,1)) ?></div> 82 <span><?= e(cfg('site_name','Nexus Forum')) ?></span> 83 </a> 84 <h1>Create an account</h1> 85 <p class="auth-sub">Join the community</p> 86 87 <?php if (!empty($errs)): ?> 88 <div class="alert err"><?= implode('<br>', array_map('e', $errs)) ?></div> 89 <?php endif; ?> 90 91 <?php if (isset($disabled)): ?> 92 <div class="alert warn">Registration is currently disabled.</div> 93 <?php else: ?> 94 <form method="POST" autocomplete="off"> 95 <?= csrf_input() ?> 96 <div class="fg"> 97 <label>Username</label> 98 <input type="text" name="username" class="fi" required autofocus 99 value="<?= e($_POST['username'] ?? '') ?>" 100 placeholder="your_username" minlength="3" maxlength="30"> 101 <span class="hint">Letters, numbers, _ and - only</span> 102 </div> 103 <div class="fg"> 104 <label>Email</label> 105 <input type="email" name="email" class="fi" required 106 value="<?= e($_POST['email'] ?? '') ?>" placeholder="you@example.com"> 107 </div> 108 <div class="fg"> 109 <label>Password</label> 110 <div class="pw-row"> 111 <input type="password" id="pw1" name="password" class="fi" required 112 placeholder="Min. 8 characters" minlength="8"> 113 <button type="button" class="pw-eye" onclick="togglePwd('pw1')">๐</button> 114 </div> 115 <div class="pw-bar"><div class="pw-fill" id="pwFill"></div></div> 116 <span class="hint" id="pwHint"></span> 117 </div> 118 <div class="fg"> 119 <label>Confirm Password</label> 120 <div class="pw-row"> 121 <input type="password" id="pw2" name="password2" class="fi" required 122 placeholder="Repeat password"> 123 <button type="button" class="pw-eye" onclick="togglePwd('pw2')">๐</button> 124 </div> 125 </div> 126 127 <!-- Math Captcha --> 128 <div class="captcha-box"> 129 <div class="captcha-label"> 130 ๐ Security check โ What is 131 <strong class="captcha-q"><?= e($captcha['q']) ?></strong> 132 </div> 133 <input type="number" name="captcha" class="fi captcha-input" 134 required placeholder="Your answer" autocomplete="off"> 135 <span class="hint">Solve this simple math problem to continue</span> 136 </div> 137 138 <button type="submit" class="btn-primary btn-block" style="margin-top:16px"> 139 Create Account 140 </button> 141 </form> 142 <?php endif; ?> 143 <p class="auth-foot">Have an account? <a href="<?= u('auth/login.php') ?>">Sign in โ</a></p> 144 </div> 145 <p class="auth-back"><a href="<?= u('/') ?>">โ Back to forum</a></p> 146 </div> 147 <script> 148 function togglePwd(id) { 149 var e = document.getElementById(id); 150 e.type = e.type === 'password' ? 'text' : 'password'; 151 } 152 var pw = document.getElementById('pw1'); 153 if (pw) pw.addEventListener('input', function() { 154 var p=this.value, f=document.getElementById('pwFill'), h=document.getElementById('pwHint'), s=0; 155 if (p.length >= 8) s++; 156 if (p.length >= 12) s++; 157 if (/[A-Z]/.test(p)) s++; 158 if (/[0-9]/.test(p)) s++; 159 if (/[^A-Za-z0-9]/.test(p)) s++; 160 f.style.width = (s / 5 * 100) + '%'; 161 var cols = ['','#ef4444','#f59e0b','#f59e0b','#22c55e','#22c55e']; 162 f.style.background = cols[s] || '#22c55e'; 163 h.textContent = ['','Weak','Fair','Good','Strong','Very strong'][s] || ''; 164 h.style.color = f.style.background; 165 }); 166 </script> 167 </body> 168 </html>