diff --git a/app/main.py b/app/main.py index 8107f1e..583de5b 100644 --- a/app/main.py +++ b/app/main.py @@ -547,7 +547,8 @@ def register_email_domain_block(domain: str, acct: str) -> None: log.error("failed to register email_domain_block for %s (acct=%s): %s", domain, acct, exc) -def process_signup(account_id: str, acct: str, ip: str, email: str = "") -> None: +def process_signup(account_id: str, acct: str, ip: str, email: str = "", + approved: bool = False) -> None: """Classify a new signup's IP and email domain, and act on account.created. Non-flagged (or both signals disabled/unavailable) signups are welcomed @@ -555,7 +556,10 @@ def process_signup(account_id: str, acct: str, ip: str, email: str = "") -> None either signal (datacenter IP and/or high-risk email domain) gets a moderator DM, the matching auto-block(s) (ip_block / email_domain_block), and — unless dry-run — has its welcome held until account.approved fires - (see _handle_account_created). + (see _handle_account_created). `approved` is the account's *current* + Admin::Account.approved state at signup time: if it's already true (open + registration, never queued), account.approved will never be delivered for + it, so the hold is lifted immediately instead of waiting forever. """ reasons: list[str] = [] hold = False @@ -599,11 +603,15 @@ def process_signup(account_id: str, acct: str, ip: str, email: str = "") -> None + (" Welcome DM held pending approval." if hold else "") ) - if not hold: + if not hold or approved: + # Either never held, or held but Mastodon already auto-approved this + # signup (open registration, no moderator queue) — account.approved + # is not coming, so act now instead of waiting for it. send_welcome(account_id, acct) maybe_start_suspicious_watch(account_id, acct) - # else: held — sent later if/when account.approved fires (human review), - # which is also where maybe_start_suspicious_watch gets called. + # else: genuinely queued for a moderator's decision — sent later if/when + # account.approved fires (human review), which is also where + # maybe_start_suspicious_watch gets called. def maybe_start_suspicious_watch(account_id: str, acct: str) -> None: @@ -1063,7 +1071,12 @@ def _handle_account_created(obj: dict, background: BackgroundTasks, event: str): # decide whether to welcome now or hold for account.approved. ip = obj.get("ip") or "" email = obj.get("email") or "" - background.add_task(process_signup, account_id, acct, ip, email) + # Admin::Account.approved is already true here for any signup that + # Mastodon auto-approved as part of open registration (no moderator + # queue involved) — account.approved never fires for those, so + # process_signup must act now instead of waiting for it. + approved = bool(obj.get("approved")) + background.add_task(process_signup, account_id, acct, ip, email, approved) else: # account.approved: a human has cleared the approval-required # registration gate. Always welcome (dedup makes this safe) — this is diff --git a/test_local.py b/test_local.py index 45e3caf..fc5d764 100644 --- a/test_local.py +++ b/test_local.py @@ -263,6 +263,17 @@ def ip_scrutiny_tests(): main.send_welcome("102", "dc1") # simulate account.approved's unconditional welcome assert ("102", "dc1") in sent, sent + # B2. datacenter IP, held, but Mastodon already auto-approved the signup + # (open registration, never queued) -> account.approved will never + # fire, so process_signup must welcome + start the watch immediately + # instead of waiting for an event that isn't coming. + sent.clear(); dms.clear(); ipblocks.clear() + classifications["198.51.100.22"] = ("datacenter", "Example Cloud Hosting Inc", True) + main.process_signup("104", "dc3", "198.51.100.22", "", True) + assert ("104", "dc3") in sent, "already-approved flagged signup must be welcomed immediately" + assert main.get_signup_flag("104") is True + assert main.already_watched_suspicious("104"), "already-approved flagged signup must start its watch immediately" + # C. same datacenter IP, but IP_SCRUTINY_DRY_RUN -> welcomed immediately # (no hold), no ip-block write, DM carries the dry-run prefix. sent.clear(); dms.clear(); ipblocks.clear()