diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..04dd8ca --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,24 @@ +name: Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: node:20-bookworm + volumes: + - /mnt/deploy:/mnt/deploy + steps: + - uses: actions/checkout@v4 + + - name: Publish to docroot + run: | + find /mnt/deploy -mindepth 1 -delete + cp -R . /mnt/deploy/ + rm -rf /mnt/deploy/.git /mnt/deploy/.gitea + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + echo "${{ github.run_number }} ($SHORT_SHA)" > /mnt/deploy/build.txt + find /mnt/deploy -type d -exec chmod 755 {} + + find /mnt/deploy -type f -exec chmod 644 {} + diff --git a/guestbook.php b/guestbook.php index 95ba906..f8ea795 100644 --- a/guestbook.php +++ b/guestbook.php @@ -1,5 +1,6 @@ exec('CREATE TABLE IF NOT EXISTS entries ( msg TEXT NOT NULL, ts INTEGER NOT NULL )'); +$db->exec('CREATE TABLE IF NOT EXISTS ratelimit ( + ip_hash TEXT PRIMARY KEY, + last_ts INTEGER NOT NULL +)'); $isHtmx = isset($_SERVER['HTTP_HX_REQUEST']); +$notice = ''; + +function client_ip(): string { + $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ''; + if ($xff !== '') { + return trim(explode(',', $xff)[0]); + } + return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; +} if ($_SERVER['REQUEST_METHOD'] === 'POST') { $name = trim($_POST['name'] ?? ''); $msg = trim($_POST['msg'] ?? ''); - if ($name !== '' && $msg !== '') { - $stmt = $db->prepare('INSERT INTO entries (name, msg, ts) VALUES (?, ?, ?)'); - $stmt->execute([mb_substr($name, 0, 40), mb_substr($msg, 0, 500), time()]); + $trap = trim($_POST['url'] ?? ''); + $ok = false; + + if ($trap !== '') { + $ok = true; + } elseif ($name === '' || $msg === '') { + $notice = 'name and message are required.'; + } elseif (gb_has_link($msg)) { + $notice = 'links aren\'t allowed.'; + } else { + $ipHash = hash('sha256', client_ip() . '|guestbook'); + $now = time(); + $window = 30; + $stmt = $db->prepare('SELECT last_ts FROM ratelimit WHERE ip_hash = ?'); + $stmt->execute([$ipHash]); + $last = (int) $stmt->fetchColumn(); + + if ($last && $now - $last < $window) { + $notice = 'you\'re posting too fast — wait a moment.'; + } else { + $ins = $db->prepare('INSERT INTO entries (name, msg, ts) VALUES (?, ?, ?)'); + $ins->execute([ + gb_censor(mb_substr($name, 0, 40)), + gb_censor(mb_substr($msg, 0, 250)), + $now, + ]); + $db->prepare('INSERT INTO ratelimit (ip_hash, last_ts) VALUES (?, ?) + ON CONFLICT(ip_hash) DO UPDATE SET last_ts = excluded.last_ts') + ->execute([$ipHash, $now]); + $ok = true; + } } - if (!$isHtmx) { + + if (!$isHtmx && $ok) { header('Location: /guestbook.php'); exit; } @@ -39,6 +82,9 @@ function render_entries(iterable $rows): void { } if ($isHtmx) { + if ($notice !== '') { + echo '

' . htmlspecialchars($notice) . '

'; + } render_entries($rows); exit; } @@ -47,10 +93,16 @@ require __DIR__ . '/inc/header.php'; ?>

guestbook

+ +

+ +
- + +
diff --git a/inc/moderation.php b/inc/moderation.php new file mode 100644 index 0000000..34befc9 --- /dev/null +++ b/inc/moderation.php @@ -0,0 +1,28 @@ +