Files
yttrx-welcomebot/nginx/hooks.yttrx.com.conf
T
pmb 57234ca548
docker-build-push / build-push (push) Successful in 6s
Add a reputation query API for pre-signup blocking
Everything the bot does today is reactive: Mastodon creates the account,
the account.created webhook arrives, we classify the signup IP and email
domain, and then we block the range and suspend what already exists.

POST /api/reputation is the pre-emptive half. A caller asks about an IP
and/or email domain; we classify it, record the answer, register the
matching Admin::IpBlock / EmailDomainBlock, and only then return. A
caller that runs its own block lookup immediately afterwards therefore
finds a block that did not exist when its request started. The intended
caller is Mastodon's signup path, which does exactly that.

It writes, so unlike /api/blocks it is authenticated and refuses to serve
without a key (REPUTATION_API_KEY): an unauthenticated endpoint that
installs IP blocks is a denial-of-service lever, so there is no open mode.

Notes on the pieces that aren't obvious:

- REPUTATION_API_MAX_LOOKUPS_PER_HOUR caps *fresh* provider lookups. The
  caller hits this on every view of the signup form, not just on
  submission, so without a ceiling a crawler walking /auth/sign_up from a
  new range each time would set our ipapi.is bill. Over budget we answer
  "unknown" and install nothing -- never "flagged". Cache hits are free
  and uncounted, and the webhook path never spends from this budget.

- Blocks installed here are recorded in their own table, keyed by the
  CIDR/domain rather than by account (there is no account yet), and are
  folded into /api/blocks and the reconcile pass so neither one
  under-reports as blocking shifts to the pre-signup path. The published
  entry shape is unchanged; a range blocked before anyone signed up from
  it simply reads "signups": 0.

- A repeat query for an already-installed block skips the Mastodon round
  trip. This runs on a live signup request, so that is saved latency.
2026-09-13 16:02:26 -07:00

72 lines
2.7 KiB
Plaintext

# /etc/nginx/sites-available/hooks on admin.yttrx.com
# Enable with: ln -s ../sites-available/hooks /etc/nginx/sites-enabled/hooks
server {
listen 80;
listen [::]:80;
server_name hooks.yttrx.com;
# certbot --nginx will manage the redirect / cert; left here for the
# initial standalone issuance flow described in the README.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name hooks.yttrx.com;
ssl_certificate /etc/letsencrypt/live/hooks.yttrx.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hooks.yttrx.com/privkey.pem;
# Only the webhook endpoint and health check are exposed.
location = /webhook {
proxy_pass http://127.0.0.1:8087;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /healthz {
proxy_pass http://127.0.0.1:8087;
}
# Read-only blocklist API (GET /api/blocks, /api/blocks/ip-ranges,
# /api/blocks/email-domains). Unauthenticated by design, and gated in the
# app by BLOCKS_API_ENABLED — with that unset the app 404s these paths,
# so proxying them here exposes nothing on its own.
location ^~ /api/blocks {
proxy_pass http://127.0.0.1:8087;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Reputation query API (POST /api/reputation). Called by Mastodon on
# mammut during signup, so it has to be reachable from off-box — unlike
# /webhook this one is authenticated with a shared secret
# (X-Reputation-Key) rather than an HMAC over the body, and it is gated in
# the app by REPUTATION_API_ENABLED + REPUTATION_API_KEY: with either
# unset the app 404s this path.
#
# It sits on a live signup request, so the proxy timeouts are short: the
# caller gives up after ~2s anyway and proceeds without an answer, and a
# request left hanging here would just hold a Puma thread on mammut for no
# benefit.
location = /api/reputation {
proxy_pass http://127.0.0.1:8087;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 2s;
proxy_send_timeout 5s;
proxy_read_timeout 5s;
}
location / {
return 404;
}
}