diff --git a/plugin/floonet_writepolicy.py b/plugin/floonet_writepolicy.py index 702cf6c..01b5768 100755 --- a/plugin/floonet_writepolicy.py +++ b/plugin/floonet_writepolicy.py @@ -27,11 +27,46 @@ their events with a `-` tag: first attempt triggers the challenge, the client AUTHs, then republishes. Verified end to end against strfry b80cda3a812af1b662223edad47eb70b053508b6. +This relay serves two apps, so the whitelist is the union of their kinds +(default-deny for everything else). Goblin wallet kinds: + + 0 profile metadata (NIP-01) + 3 contact list (NIP-02) + 5 event deletion (NIP-09) + 13 seal: inner sealed event of a gift wrap (NIP-59) + 1059 gift wrap: sealed DMs and payments (NIP-59) + 10002 relay list metadata (NIP-65) + 10050 DM relay list (NIP-17) + 27235 HTTP auth event for the name authority (NIP-98) + +Magick Market marketplace kinds (also reuses 0/5/1059/10002 above): + + 1 text note: bug reports, shared listings (NIP-01) + 7 reaction (NIP-25) + 14 order chat / general order message, plaintext (Gamma spec) + 16 order processing and status update (Gamma spec) + 17 payment receipt / confirmation (Gamma spec) + 1111 comment (NIP-22) + 10000 mute list, used as merchant/product blacklist (NIP-51) + 30000 people set: admins, editors, featured users, vanity, NIP-05 (NIP-51) + 30003 bookmark set: featured collections (NIP-51) + 30078 app-specific data: cart, relay prefs, V4V (NIP-78) + 30402 product listing (NIP-99) + 30405 product collection / featured products (Gamma spec) + 30406 shipping option (Gamma spec) + 31990 handler information (NIP-89) + 24133 NIP-46 remote signing (Nostr Connect, ephemeral — Goblin wallet login) + +Excluded on purpose: 25910 (ContextVM) only ever rides inside a 1059 gift +wrap, never raw; 30017/30018 (legacy NIP-15) are read from sellers' own relays +during migration, never written here; 9735 (Lightning zap receipt) is dead in +this GRIN-only fork. + Configuration is environment variables (set them on the strfry process; the plugin inherits them, e.g. via docker compose or the systemd unit): - FLOONET_ALLOWED_KINDS comma-separated kind whitelist - [default: 0,3,5,13,1059,10002,10050,27235] + FLOONET_ALLOWED_KINDS comma-separated kind whitelist [default: the + Goblin + Magick Market set documented above] FLOONET_REQUIRE_AUTH true/false [default: false] FLOONET_PAY_MODE off|name|write [default: off] (only "write" changes plugin behavior; "name" is @@ -53,7 +88,10 @@ import sys import time import urllib.request -DEFAULT_ALLOWED_KINDS = "0,3,5,13,1059,10002,10050,27235" +DEFAULT_ALLOWED_KINDS = ( + "0,1,3,5,7,13,14,16,17,1059,1111,10000,10002,10050,24133,27235," + "30000,30003,30078,30402,30405,30406,31990" +) def load_config(env=os.environ): diff --git a/plugin/test_policy.py b/plugin/test_policy.py index fa5af2f..85fd409 100644 --- a/plugin/test_policy.py +++ b/plugin/test_policy.py @@ -23,7 +23,10 @@ import floonet_writepolicy as wp PLUGIN = os.path.join(os.path.dirname(os.path.abspath(__file__)), "floonet_writepolicy.py") PK = "a" * 64 -DEFAULT_KINDS = (0, 3, 5, 13, 1059, 10002, 10050, 27235) +DEFAULT_KINDS = ( + 0, 1, 3, 5, 7, 13, 14, 16, 17, 1059, 1111, 10000, 10002, 10050, 24133, + 27235, 30000, 30003, 30078, 30402, 30405, 30406, 31990, +) def req(kind, authed=None, event_id="e1"): @@ -54,11 +57,23 @@ class KindWhitelist(unittest.TestCase): self.assertEqual(reply["id"], "e1") def test_disallowed_kinds_rejected(self): - for kind in (1, 4, 6, 7, 14, 1058, 1060, 30023, 22242, -1): + # 25910 (ContextVM) rides inside 1059 gift wraps only; + # 30017/30018 (legacy NIP-15) come from sellers' own relays; + # 9735 (zap) is dead in the GRIN-only fork. All stay rejected. + for kind in (4, 6, 9735, 1058, 1060, 25910, 30017, 30018, 30023, 22242, -1): reply = wp.decide(req(kind), cfg()) self.assertEqual(reply["action"], "reject", "kind %d" % kind) self.assertIn("kind not accepted", reply["msg"]) + def test_marketplace_kind_accepted_and_zap_rejected(self): + # A newly-allowed Magick Market kind (NIP-89 handler info) is accepted. + self.assertEqual(wp.decide(req(31990), cfg())["action"], "accept") + # A still-rejected kind (Lightning zap receipt, disabled in the + # GRIN-only marketplace) is refused by the default-deny whitelist. + reply = wp.decide(req(9735), cfg()) + self.assertEqual(reply["action"], "reject") + self.assertIn("kind not accepted", reply["msg"]) + def test_malformed_kind_fails_closed(self): for bad in (None, "1059", 3.5, True, [1059]): r = req(0) @@ -174,7 +189,7 @@ class PaidWriteGate(unittest.TestCase): def test_kind_check_still_first_in_write_mode(self): _Authority.paid_pubkeys = {PK} - reply = wp.decide(req(1, authed=PK), self.c()) + reply = wp.decide(req(9735, authed=PK), self.c()) self.assertEqual(reply["action"], "reject") self.assertIn("kind not accepted", reply["msg"]) @@ -199,7 +214,7 @@ class StrfryPipeProtocol(unittest.TestCase): return [json.loads(out) for out in proc.stdout.splitlines()] def test_accept_and_reject_over_the_wire(self): - replies = self.run_plugin([req(1059, event_id="ok1"), req(1, event_id="no1"), req(0, event_id="ok2")]) + replies = self.run_plugin([req(1059, event_id="ok1"), req(9735, event_id="no1"), req(0, event_id="ok2")]) self.assertEqual( [(r["id"], r["action"]) for r in replies], [("ok1", "accept"), ("no1", "reject"), ("ok2", "reject" if 0 not in DEFAULT_KINDS else "accept")],