2.4 KiB
2.4 KiB
Context
The alert path already has location-aware throttling + fingerprint blocking. This change adds cost protection and the 2014 paid-gating model using existing schema only (no Stripe). The 2014 code suspended SMS on is_paid/late-payment; our equivalent is orders.status != 'paid'.
Goals / Non-Goals
Goals:
- Non-paid orders ⇒ no alert SMS (record scans only).
- Per-tag daily alert cap + per-IP hourly rate limit (configurable, env-overridable).
- Zero new schema, zero external services.
Non-Goals:
- Payment gateway (Stripe/Paddle) — later; it will simply set
orders.status. - Plan tiers/entitlements beyond the daily/hourly caps.
- Admin UI changes (TagResource already shows the order relation).
Decisions
- Paid-gating in
shouldAlert: load the tag's order (viatag.order_id+ aGetOrderByIDquery); if order exists andstatus != 'paid'→ false. No order → proceed (transitional — most tags aren't sales-linked yet). - Per-tag daily cap:
CountAlertsByTagSince(tagID, now()-24h);maxAlertsPerTagDaydefault 5, envALERT_MAX_TAG_DAY. - Per-IP hourly limit: need the scanner IP →
CountAlertsByIPSince(ip, now()-1h); butscanshas no IP column! Options: (a) addscans.ip TEXT(idempotent ALTER) and recordr.RemoteAddrhost; (b) skip IP limit. Adding the column is cheap and matches the 2014 model (they trackedip_address). Decision: addscans.ipcolumn (idempotent) + record the client IP;maxAlertsPerIPHourdefault 10, envALERT_MAX_IP_HOUR. - Ordering of checks in
shouldAlert: sms_enabled → owner phone → fingerprint block → paid-gating → daily cap → IP rate → 10-min window rules → send. - Client IP: from
X-Forwarded-Forif present (behind Caddy), elseRemoteAddrhost.
Risks / Trade-offs
- [Tags without orders keep alerting] → intentional transitional behaviour; documented.
- [IP capture behind proxy] → X-Forwarded-For parsing (Caddy sets it); falls back to RemoteAddr.
- [Cap constants too strict/loose] → env-overridable; no schema change to retune.
Migration Plan
db/schema.sql: idempotentALTER TABLE scans ADD COLUMN IF NOT EXISTS ip TEXT;→make db-up.- sqlc:
GetOrderByID,CountAlertsByTagSince,CountAlertsByIPSince→make generate. - Handlers: scan.go gating + IP capture;
InsertScangains IP. - Verify with extended suite; commit.
Open Questions
- None blocking.