at the very top of any protected page. * * How it works: * - Only emails pre-loaded into gate_emails.sqlite are allowed through. * - Each email may hold exactly ONE active session at a time. A new login * from any browser/device silently invalidates all previous sessions for * that email (token rotation). * - Banned emails see a 403 Denied screen regardless of session state. * - Unknown emails see a "not on the list" error with a Patreon signup nudge. * * Requirements: PHP 7.4+, PDO + pdo_sqlite extension. * Manage the allowlist with gate_admin.php. */ // ── Config ──────────────────────────────────────────────────────────────────── define('GATE_DB_PATH', __DIR__ . '/gate_emails.sqlite'); define('GATE_SESSION_KEY', 'gate_verified'); define('GATE_SESSION_EMAIL','gate_email'); define('GATE_SESSION_TOKEN','gate_token'); define('GATE_CAPTCHA_KEY', 'gate_captcha_answer'); define('GATE_CAPTCHA_Q', 'gate_captcha_q'); define('GATE_PATREON_URL', 'https://www.patreon.com/tyclifford'); // ← replace // ───────────────────────────────────────────────────────────────────────────── session_start(); // ═════════════════════════════════════════════════════════════════════════════ // DATABASE // ═════════════════════════════════════════════════════════════════════════════ function gate_db(): PDO { static $pdo = null; if ($pdo !== null) return $pdo; $pdo = new PDO('sqlite:' . GATE_DB_PATH); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->exec("PRAGMA journal_mode=WAL"); $pdo->exec("CREATE TABLE IF NOT EXISTS gate_emails ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE COLLATE NOCASE, status TEXT NOT NULL DEFAULT 'valid' CHECK(status IN ('valid','banned')), session_token TEXT DEFAULT NULL, added_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_login DATETIME DEFAULT NULL, login_ip TEXT DEFAULT NULL, login_page TEXT DEFAULT NULL )"); return $pdo; } function gate_get_record(string $email): ?array { $stmt = gate_db()->prepare( "SELECT * FROM gate_emails WHERE email = :e COLLATE NOCASE LIMIT 1" ); $stmt->execute([':e' => strtolower(trim($email))]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row ?: null; } function gate_create_session(string $email): void { $token = bin2hex(random_bytes(32)); $db = gate_db(); $stmt = $db->prepare( "UPDATE gate_emails SET session_token = :t, last_login = CURRENT_TIMESTAMP, login_ip = :ip, login_page = :p WHERE email = :e COLLATE NOCASE" ); $stmt->execute([ ':t' => $token, ':ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', ':p' => $_SERVER['REQUEST_URI'] ?? '', ':e' => strtolower(trim($email)), ]); $_SESSION[GATE_SESSION_KEY] = true; $_SESSION[GATE_SESSION_EMAIL] = strtolower(trim($email)); $_SESSION[GATE_SESSION_TOKEN] = $token; } // ═════════════════════════════════════════════════════════════════════════════ // REQUEST HANDLING // ═════════════════════════════════════════════════════════════════════════════ function gate_kill_session(): void { $_SESSION = []; if (ini_get('session.use_cookies')) { $p = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); } session_destroy(); session_start(); } if (!empty($_SESSION[GATE_SESSION_KEY])) { $sess_email = $_SESSION[GATE_SESSION_EMAIL] ?? ''; $sess_token = $_SESSION[GATE_SESSION_TOKEN] ?? ''; $record = $sess_email !== '' ? gate_get_record($sess_email) : null; if ($record === null) { gate_kill_session(); $gate_form_error = 'Your access has been removed. Please contact an administrator.'; } elseif ($record['status'] === 'banned') { gate_kill_session(); gate_render_denied($sess_email); } elseif ($record['session_token'] !== $sess_token) { gate_kill_session(); $gate_form_error = 'Your session was ended because you signed in from another location.'; } else { return; } } $gate_form_error = $gate_form_error ?? ''; $gate_show_patreon = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $email = strtolower(trim($_POST['email'] ?? '')); $answer = trim($_POST['captcha'] ?? ''); $expected = $_SESSION[GATE_CAPTCHA_KEY] ?? null; if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { $gate_form_error = 'Please enter a valid email address.'; } elseif ($expected === null || (int)$answer !== (int)$expected) { $gate_form_error = 'Incorrect answer — please try again.'; } else { $record = gate_get_record($email); if ($record === null) { $gate_form_error = 'That email isn\'t on the access list yet.'; $gate_show_patreon = true; } elseif ($record['status'] === 'banned') { gate_render_denied($email); } else { gate_create_session($email); unset($_SESSION[GATE_CAPTCHA_KEY], $_SESSION[GATE_CAPTCHA_Q]); $scheme = (($_SERVER['HTTPS'] ?? '') === 'on') ? 'https' : 'http'; header('Location: ' . $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); exit; } } } if (empty($_SESSION[GATE_CAPTCHA_KEY]) || $gate_form_error) { $a = random_int(2, 12); $b = random_int(1, 10); $_SESSION[GATE_CAPTCHA_Q] = "$a + $b"; $_SESSION[GATE_CAPTCHA_KEY] = $a + $b; } $captcha_question = $_SESSION[GATE_CAPTCHA_Q]; // ═════════════════════════════════════════════════════════════════════════════ // STYLES // ═════════════════════════════════════════════════════════════════════════════ function gate_styles(): void { ?> Access Denied
Account Suspended

Access for this email address has been revoked.

status: 403 forbidden  |  contact to appeal

Members Access
Membership via Patreon
Get free access
by joining my Patreon

This area is open to all my Patreon members — no payment required. Simply join as a free member and your email is added to the access list. You can choose to upgrade to a paid tier anytime to support the work and unlock extra perks.

Sign up at least 24 hours before your first visit. After joining Patreon, it may take up to 24 hours for your email to be synced and activated on the access list. Plan ahead!
Join Free on Patreon

Already a paid member?  Manage your tier →

Already a member

Sign in with
your Patreon email

Use the email address linked to your Patreon account.

=