0, 'message' => '']; // ── Step 1: Session guard ───────────────────────────────────────────────────── if (empty($_SESSION['invited_user_id']) || empty($_SESSION['invited_token'])) { $answer['message'] = 'Invalid session. Please use your invitation link.'; http_response_code(403); exit(json_encode($answer)); } $user_id = (int)$_SESSION['invited_user_id']; $token = $_SESSION['invited_token']; // ── Step 2: CSRF check ──────────────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { $csrf = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (empty($csrf) || $csrf !== ($_SESSION['csrf_token'] ?? '')) { http_response_code(403); $answer['message'] = 'Invalid request.'; exit(json_encode($answer)); } } $data = json_decode($_POST['json'] ?? '{}', true) ?: []; try { // ── Step 3: Re-validate token ───────────────────────────────────────────── $sth = $pdo1->prepare( "SELECT user_id FROM user WHERE user_id = :uid AND verify_token = :token AND status = 'pending' AND license = 'user' AND verify_expires_at > NOW() LIMIT 1" ); $sth->execute([':uid' => $user_id, ':token' => $token]); if (!$sth->fetchColumn()) { $answer['message'] = 'Invitation has expired or already been used. Please request a new invitation.'; http_response_code(403); exit(json_encode($answer)); } // ── Step 4: Sanitise and validate input ─────────────────────────────────── $name = trim($data['name'] ?? ''); $surname = trim($data['surname'] ?? ''); $username = strtolower(trim($data['username'] ?? '')); $password = $data['password'] ?? ''; $confirm = $data['confirm_password'] ?? ''; if (!$name || !$surname || !$username || !$password || !$confirm) { throw new Exception('All fields are required.'); } // ── Step 5: Username format and uniqueness ──────────────────────────────── if (!preg_match('/^[a-z0-9_]+$/', $username)) { throw new Exception('Username may only contain lowercase letters, numbers and underscores.'); } $sth = $pdo1->prepare("SELECT user_id FROM user WHERE username = :u AND user_id != :uid LIMIT 1"); $sth->execute([':u' => $username, ':uid' => $user_id]); if ($sth->fetchColumn()) { throw new Exception('Username is already taken. Please choose another.'); } // ── Step 6: Password match and strength ─────────────────────────────────── if ($password !== $confirm) { throw new Exception('Passwords do not match.'); } $pm = new PasswordManager($pdo1, $include_url); $result = $pm->checkStrength($password, [$name, $surname, $username]); if ($result['score'] < PasswordManager::MIN_SCORE) { $msg = $result['warning'] ?: ($result['suggestions'][0] ?? 'Please choose a stronger password.'); throw new Exception('Password is too weak. ' . $msg); } // ── Step 7–8: Hash and activate account ────────────────────────────────── $hashed = password_hash($password, PASSWORD_BCRYPT); $pdo1->prepare( "UPDATE user SET name = :name, surname = :surname, username = :username, password = :password, status = 'active', verify_token = NULL, verify_expires_at = NULL WHERE user_id = :uid" )->execute([ ':name' => $name, ':surname' => $surname, ':username' => $username, ':password' => $hashed, ':uid' => $user_id, ]); // ── Step 9: Clear invite token from company_map_user ───────────────────── $pdo1->prepare( "UPDATE company_map_user SET invite_token = NULL WHERE user_id = :uid" )->execute([':uid' => $user_id]); // ── Step 10: Clear session invite keys ──────────────────────────────────── unset($_SESSION['invited_user_id'], $_SESSION['invited_token']); $answer['success'] = 1; $answer['message'] = 'Account setup complete.'; } catch (Exception $e) { $answer['message'] = $e->getMessage(); http_response_code(400); } exit(json_encode($answer));