upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock527
-rw-r--r--grasp-audit/Cargo.toml4
-rw-r--r--grasp-audit/src/client.rs19
-rw-r--r--grasp-audit/src/specs/grasp01/nip11_document.rs189
-rw-r--r--src/http/mod.rs27
-rw-r--r--src/http/nip11.rs146
-rw-r--r--tests/nip11_document.rs62
7 files changed, 923 insertions, 51 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 71490b2..5888eb0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,6 +165,12 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
165 165
166[[package]] 166[[package]]
167name = "base64" 167name = "base64"
168version = "0.21.7"
169source = "registry+https://github.com/rust-lang/crates.io-index"
170checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
171
172[[package]]
173name = "base64"
168version = "0.22.1" 174version = "0.22.1"
169source = "registry+https://github.com/rust-lang/crates.io-index" 175source = "registry+https://github.com/rust-lang/crates.io-index"
170checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 176checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
@@ -227,6 +233,12 @@ dependencies = [
227 233
228[[package]] 234[[package]]
229name = "bitflags" 235name = "bitflags"
236version = "1.3.2"
237source = "registry+https://github.com/rust-lang/crates.io-index"
238checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
239
240[[package]]
241name = "bitflags"
230version = "2.10.0" 242version = "2.10.0"
231source = "registry+https://github.com/rust-lang/crates.io-index" 243source = "registry+https://github.com/rust-lang/crates.io-index"
232checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 244checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
@@ -381,6 +393,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
381checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 393checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
382 394
383[[package]] 395[[package]]
396name = "core-foundation"
397version = "0.9.4"
398source = "registry+https://github.com/rust-lang/crates.io-index"
399checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
400dependencies = [
401 "core-foundation-sys",
402 "libc",
403]
404
405[[package]]
384name = "core-foundation-sys" 406name = "core-foundation-sys"
385version = "0.8.7" 407version = "0.8.7"
386source = "registry+https://github.com/rust-lang/crates.io-index" 408source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -447,12 +469,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
447checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 469checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
448 470
449[[package]] 471[[package]]
472name = "encoding_rs"
473version = "0.8.35"
474source = "registry+https://github.com/rust-lang/crates.io-index"
475checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
476dependencies = [
477 "cfg-if",
478]
479
480[[package]]
450name = "equivalent" 481name = "equivalent"
451version = "1.0.2" 482version = "1.0.2"
452source = "registry+https://github.com/rust-lang/crates.io-index" 483source = "registry+https://github.com/rust-lang/crates.io-index"
453checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 484checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
454 485
455[[package]] 486[[package]]
487name = "errno"
488version = "0.3.14"
489source = "registry+https://github.com/rust-lang/crates.io-index"
490checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
491dependencies = [
492 "libc",
493 "windows-sys 0.61.2",
494]
495
496[[package]]
497name = "fastrand"
498version = "2.3.0"
499source = "registry+https://github.com/rust-lang/crates.io-index"
500checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
501
502[[package]]
456name = "find-msvc-tools" 503name = "find-msvc-tools"
457version = "0.1.4" 504version = "0.1.4"
458source = "registry+https://github.com/rust-lang/crates.io-index" 505source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -465,6 +512,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
465checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 512checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
466 513
467[[package]] 514[[package]]
515name = "foreign-types"
516version = "0.3.2"
517source = "registry+https://github.com/rust-lang/crates.io-index"
518checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
519dependencies = [
520 "foreign-types-shared",
521]
522
523[[package]]
524name = "foreign-types-shared"
525version = "0.1.1"
526source = "registry+https://github.com/rust-lang/crates.io-index"
527checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
528
529[[package]]
468name = "form_urlencoded" 530name = "form_urlencoded"
469version = "1.2.2" 531version = "1.2.2"
470source = "registry+https://github.com/rust-lang/crates.io-index" 532source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -618,6 +680,8 @@ dependencies = [
618 "clap", 680 "clap",
619 "futures", 681 "futures",
620 "nostr-sdk 0.43.0", 682 "nostr-sdk 0.43.0",
683 "regex",
684 "reqwest",
621 "serde", 685 "serde",
622 "serde_json", 686 "serde_json",
623 "thiserror 1.0.69", 687 "thiserror 1.0.69",
@@ -630,6 +694,25 @@ dependencies = [
630 694
631[[package]] 695[[package]]
632name = "h2" 696name = "h2"
697version = "0.3.27"
698source = "registry+https://github.com/rust-lang/crates.io-index"
699checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
700dependencies = [
701 "bytes",
702 "fnv",
703 "futures-core",
704 "futures-sink",
705 "futures-util",
706 "http 0.2.12",
707 "indexmap",
708 "slab",
709 "tokio",
710 "tokio-util",
711 "tracing",
712]
713
714[[package]]
715name = "h2"
633version = "0.4.12" 716version = "0.4.12"
634source = "registry+https://github.com/rust-lang/crates.io-index" 717source = "registry+https://github.com/rust-lang/crates.io-index"
635checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 718checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
@@ -639,7 +722,7 @@ dependencies = [
639 "fnv", 722 "fnv",
640 "futures-core", 723 "futures-core",
641 "futures-sink", 724 "futures-sink",
642 "http", 725 "http 1.3.1",
643 "indexmap", 726 "indexmap",
644 "slab", 727 "slab",
645 "tokio", 728 "tokio",
@@ -691,6 +774,17 @@ dependencies = [
691 774
692[[package]] 775[[package]]
693name = "http" 776name = "http"
777version = "0.2.12"
778source = "registry+https://github.com/rust-lang/crates.io-index"
779checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
780dependencies = [
781 "bytes",
782 "fnv",
783 "itoa",
784]
785
786[[package]]
787name = "http"
694version = "1.3.1" 788version = "1.3.1"
695source = "registry+https://github.com/rust-lang/crates.io-index" 789source = "registry+https://github.com/rust-lang/crates.io-index"
696checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 790checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
@@ -702,12 +796,23 @@ dependencies = [
702 796
703[[package]] 797[[package]]
704name = "http-body" 798name = "http-body"
799version = "0.4.6"
800source = "registry+https://github.com/rust-lang/crates.io-index"
801checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
802dependencies = [
803 "bytes",
804 "http 0.2.12",
805 "pin-project-lite",
806]
807
808[[package]]
809name = "http-body"
705version = "1.0.1" 810version = "1.0.1"
706source = "registry+https://github.com/rust-lang/crates.io-index" 811source = "registry+https://github.com/rust-lang/crates.io-index"
707checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 812checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
708dependencies = [ 813dependencies = [
709 "bytes", 814 "bytes",
710 "http", 815 "http 1.3.1",
711] 816]
712 817
713[[package]] 818[[package]]
@@ -718,8 +823,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
718dependencies = [ 823dependencies = [
719 "bytes", 824 "bytes",
720 "futures-core", 825 "futures-core",
721 "http", 826 "http 1.3.1",
722 "http-body", 827 "http-body 1.0.1",
723 "pin-project-lite", 828 "pin-project-lite",
724] 829]
725 830
@@ -737,6 +842,30 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
737 842
738[[package]] 843[[package]]
739name = "hyper" 844name = "hyper"
845version = "0.14.32"
846source = "registry+https://github.com/rust-lang/crates.io-index"
847checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
848dependencies = [
849 "bytes",
850 "futures-channel",
851 "futures-core",
852 "futures-util",
853 "h2 0.3.27",
854 "http 0.2.12",
855 "http-body 0.4.6",
856 "httparse",
857 "httpdate",
858 "itoa",
859 "pin-project-lite",
860 "socket2 0.5.10",
861 "tokio",
862 "tower-service",
863 "tracing",
864 "want",
865]
866
867[[package]]
868name = "hyper"
740version = "1.8.1" 869version = "1.8.1"
741source = "registry+https://github.com/rust-lang/crates.io-index" 870source = "registry+https://github.com/rust-lang/crates.io-index"
742checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 871checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
@@ -745,9 +874,9 @@ dependencies = [
745 "bytes", 874 "bytes",
746 "futures-channel", 875 "futures-channel",
747 "futures-core", 876 "futures-core",
748 "h2", 877 "h2 0.4.12",
749 "http", 878 "http 1.3.1",
750 "http-body", 879 "http-body 1.0.1",
751 "httparse", 880 "httparse",
752 "httpdate", 881 "httpdate",
753 "itoa", 882 "itoa",
@@ -759,6 +888,19 @@ dependencies = [
759] 888]
760 889
761[[package]] 890[[package]]
891name = "hyper-tls"
892version = "0.5.0"
893source = "registry+https://github.com/rust-lang/crates.io-index"
894checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
895dependencies = [
896 "bytes",
897 "hyper 0.14.32",
898 "native-tls",
899 "tokio",
900 "tokio-native-tls",
901]
902
903[[package]]
762name = "hyper-util" 904name = "hyper-util"
763version = "0.1.18" 905version = "0.1.18"
764source = "registry+https://github.com/rust-lang/crates.io-index" 906source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -766,9 +908,9 @@ checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
766dependencies = [ 908dependencies = [
767 "bytes", 909 "bytes",
768 "futures-core", 910 "futures-core",
769 "http", 911 "http 1.3.1",
770 "http-body", 912 "http-body 1.0.1",
771 "hyper", 913 "hyper 1.8.1",
772 "pin-project-lite", 914 "pin-project-lite",
773 "tokio", 915 "tokio",
774] 916]
@@ -932,6 +1074,12 @@ dependencies = [
932] 1074]
933 1075
934[[package]] 1076[[package]]
1077name = "ipnet"
1078version = "2.11.0"
1079source = "registry+https://github.com/rust-lang/crates.io-index"
1080checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
1081
1082[[package]]
935name = "is_terminal_polyfill" 1083name = "is_terminal_polyfill"
936version = "1.70.2" 1084version = "1.70.2"
937source = "registry+https://github.com/rust-lang/crates.io-index" 1085source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -966,6 +1114,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
966checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 1114checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
967 1115
968[[package]] 1116[[package]]
1117name = "linux-raw-sys"
1118version = "0.11.0"
1119source = "registry+https://github.com/rust-lang/crates.io-index"
1120checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
1121
1122[[package]]
969name = "litemap" 1123name = "litemap"
970version = "0.8.1" 1124version = "0.8.1"
971source = "registry+https://github.com/rust-lang/crates.io-index" 1125source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1008,6 +1162,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1008checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1162checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
1009 1163
1010[[package]] 1164[[package]]
1165name = "mime"
1166version = "0.3.17"
1167source = "registry+https://github.com/rust-lang/crates.io-index"
1168checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1169
1170[[package]]
1011name = "mio" 1171name = "mio"
1012version = "1.1.0" 1172version = "1.1.0"
1013source = "registry+https://github.com/rust-lang/crates.io-index" 1173source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1019,6 +1179,23 @@ dependencies = [
1019] 1179]
1020 1180
1021[[package]] 1181[[package]]
1182name = "native-tls"
1183version = "0.2.14"
1184source = "registry+https://github.com/rust-lang/crates.io-index"
1185checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
1186dependencies = [
1187 "libc",
1188 "log",
1189 "openssl",
1190 "openssl-probe",
1191 "openssl-sys",
1192 "schannel",
1193 "security-framework",
1194 "security-framework-sys",
1195 "tempfile",
1196]
1197
1198[[package]]
1022name = "negentropy" 1199name = "negentropy"
1023version = "0.5.0" 1200version = "0.5.0"
1024source = "registry+https://github.com/rust-lang/crates.io-index" 1201source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1029,12 +1206,12 @@ name = "ngit-grasp"
1029version = "0.1.0" 1206version = "0.1.0"
1030dependencies = [ 1207dependencies = [
1031 "anyhow", 1208 "anyhow",
1032 "base64", 1209 "base64 0.22.1",
1033 "dotenvy", 1210 "dotenvy",
1034 "futures-util", 1211 "futures-util",
1035 "grasp-audit", 1212 "grasp-audit",
1036 "http-body-util", 1213 "http-body-util",
1037 "hyper", 1214 "hyper 1.8.1",
1038 "hyper-util", 1215 "hyper-util",
1039 "nostr-relay-builder", 1216 "nostr-relay-builder",
1040 "nostr-sdk 0.44.1", 1217 "nostr-sdk 0.44.1",
@@ -1054,7 +1231,7 @@ version = "0.43.1"
1054source = "registry+https://github.com/rust-lang/crates.io-index" 1231source = "registry+https://github.com/rust-lang/crates.io-index"
1055checksum = "62a97d745f1bd8d5e05a978632bbb87b0614567d5142906fe7c86fb2440faac6" 1232checksum = "62a97d745f1bd8d5e05a978632bbb87b0614567d5142906fe7c86fb2440faac6"
1056dependencies = [ 1233dependencies = [
1057 "base64", 1234 "base64 0.22.1",
1058 "bech32", 1235 "bech32",
1059 "bip39", 1236 "bip39",
1060 "bitcoin_hashes 0.14.0", 1237 "bitcoin_hashes 0.14.0",
@@ -1077,7 +1254,7 @@ version = "0.44.1"
1077source = "registry+https://github.com/rust-lang/crates.io-index" 1254source = "registry+https://github.com/rust-lang/crates.io-index"
1078checksum = "d3595fecf0e0aaacb69a0dc0101a4453f3c76eda333d6bbc49f68f64390b3d85" 1255checksum = "d3595fecf0e0aaacb69a0dc0101a4453f3c76eda333d6bbc49f68f64390b3d85"
1079dependencies = [ 1256dependencies = [
1080 "base64", 1257 "base64 0.22.1",
1081 "bech32", 1258 "bech32",
1082 "bip39", 1259 "bip39",
1083 "bitcoin_hashes 0.14.0", 1260 "bitcoin_hashes 0.14.0",
@@ -1243,6 +1420,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1243checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1420checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
1244 1421
1245[[package]] 1422[[package]]
1423name = "openssl"
1424version = "0.10.75"
1425source = "registry+https://github.com/rust-lang/crates.io-index"
1426checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
1427dependencies = [
1428 "bitflags 2.10.0",
1429 "cfg-if",
1430 "foreign-types",
1431 "libc",
1432 "once_cell",
1433 "openssl-macros",
1434 "openssl-sys",
1435]
1436
1437[[package]]
1438name = "openssl-macros"
1439version = "0.1.1"
1440source = "registry+https://github.com/rust-lang/crates.io-index"
1441checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
1442dependencies = [
1443 "proc-macro2",
1444 "quote",
1445 "syn",
1446]
1447
1448[[package]]
1449name = "openssl-probe"
1450version = "0.1.6"
1451source = "registry+https://github.com/rust-lang/crates.io-index"
1452checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
1453
1454[[package]]
1455name = "openssl-sys"
1456version = "0.9.111"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1458checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
1459dependencies = [
1460 "cc",
1461 "libc",
1462 "pkg-config",
1463 "vcpkg",
1464]
1465
1466[[package]]
1246name = "parking_lot" 1467name = "parking_lot"
1247version = "0.12.5" 1468version = "0.12.5"
1248source = "registry+https://github.com/rust-lang/crates.io-index" 1469source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1305,6 +1526,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1305checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1526checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1306 1527
1307[[package]] 1528[[package]]
1529name = "pkg-config"
1530version = "0.3.32"
1531source = "registry+https://github.com/rust-lang/crates.io-index"
1532checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1533
1534[[package]]
1308name = "poly1305" 1535name = "poly1305"
1309version = "0.8.0" 1536version = "0.8.0"
1310source = "registry+https://github.com/rust-lang/crates.io-index" 1537source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1422,7 +1649,19 @@ version = "0.5.18"
1422source = "registry+https://github.com/rust-lang/crates.io-index" 1649source = "registry+https://github.com/rust-lang/crates.io-index"
1423checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 1650checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1424dependencies = [ 1651dependencies = [
1425 "bitflags", 1652 "bitflags 2.10.0",
1653]
1654
1655[[package]]
1656name = "regex"
1657version = "1.12.2"
1658source = "registry+https://github.com/rust-lang/crates.io-index"
1659checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
1660dependencies = [
1661 "aho-corasick",
1662 "memchr",
1663 "regex-automata",
1664 "regex-syntax",
1426] 1665]
1427 1666
1428[[package]] 1667[[package]]
@@ -1443,6 +1682,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1443checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1682checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1444 1683
1445[[package]] 1684[[package]]
1685name = "reqwest"
1686version = "0.11.27"
1687source = "registry+https://github.com/rust-lang/crates.io-index"
1688checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
1689dependencies = [
1690 "base64 0.21.7",
1691 "bytes",
1692 "encoding_rs",
1693 "futures-core",
1694 "futures-util",
1695 "h2 0.3.27",
1696 "http 0.2.12",
1697 "http-body 0.4.6",
1698 "hyper 0.14.32",
1699 "hyper-tls",
1700 "ipnet",
1701 "js-sys",
1702 "log",
1703 "mime",
1704 "native-tls",
1705 "once_cell",
1706 "percent-encoding",
1707 "pin-project-lite",
1708 "rustls-pemfile",
1709 "serde",
1710 "serde_json",
1711 "serde_urlencoded",
1712 "sync_wrapper",
1713 "system-configuration",
1714 "tokio",
1715 "tokio-native-tls",
1716 "tower-service",
1717 "url",
1718 "wasm-bindgen",
1719 "wasm-bindgen-futures",
1720 "web-sys",
1721 "winreg",
1722]
1723
1724[[package]]
1446name = "ring" 1725name = "ring"
1447version = "0.17.14" 1726version = "0.17.14"
1448source = "registry+https://github.com/rust-lang/crates.io-index" 1727source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1457,6 +1736,19 @@ dependencies = [
1457] 1736]
1458 1737
1459[[package]] 1738[[package]]
1739name = "rustix"
1740version = "1.1.2"
1741source = "registry+https://github.com/rust-lang/crates.io-index"
1742checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
1743dependencies = [
1744 "bitflags 2.10.0",
1745 "errno",
1746 "libc",
1747 "linux-raw-sys",
1748 "windows-sys 0.61.2",
1749]
1750
1751[[package]]
1460name = "rustls" 1752name = "rustls"
1461version = "0.23.34" 1753version = "0.23.34"
1462source = "registry+https://github.com/rust-lang/crates.io-index" 1754source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1471,6 +1763,15 @@ dependencies = [
1471] 1763]
1472 1764
1473[[package]] 1765[[package]]
1766name = "rustls-pemfile"
1767version = "1.0.4"
1768source = "registry+https://github.com/rust-lang/crates.io-index"
1769checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
1770dependencies = [
1771 "base64 0.21.7",
1772]
1773
1774[[package]]
1474name = "rustls-pki-types" 1775name = "rustls-pki-types"
1475version = "1.13.0" 1776version = "1.13.0"
1476source = "registry+https://github.com/rust-lang/crates.io-index" 1777source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1512,6 +1813,15 @@ dependencies = [
1512] 1813]
1513 1814
1514[[package]] 1815[[package]]
1816name = "schannel"
1817version = "0.1.28"
1818source = "registry+https://github.com/rust-lang/crates.io-index"
1819checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
1820dependencies = [
1821 "windows-sys 0.61.2",
1822]
1823
1824[[package]]
1515name = "scopeguard" 1825name = "scopeguard"
1516version = "1.2.0" 1826version = "1.2.0"
1517source = "registry+https://github.com/rust-lang/crates.io-index" 1827source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1550,6 +1860,29 @@ dependencies = [
1550] 1860]
1551 1861
1552[[package]] 1862[[package]]
1863name = "security-framework"
1864version = "2.11.1"
1865source = "registry+https://github.com/rust-lang/crates.io-index"
1866checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
1867dependencies = [
1868 "bitflags 2.10.0",
1869 "core-foundation",
1870 "core-foundation-sys",
1871 "libc",
1872 "security-framework-sys",
1873]
1874
1875[[package]]
1876name = "security-framework-sys"
1877version = "2.15.0"
1878source = "registry+https://github.com/rust-lang/crates.io-index"
1879checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
1880dependencies = [
1881 "core-foundation-sys",
1882 "libc",
1883]
1884
1885[[package]]
1553name = "serde" 1886name = "serde"
1554version = "1.0.228" 1887version = "1.0.228"
1555source = "registry+https://github.com/rust-lang/crates.io-index" 1888source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1593,6 +1926,18 @@ dependencies = [
1593] 1926]
1594 1927
1595[[package]] 1928[[package]]
1929name = "serde_urlencoded"
1930version = "0.7.1"
1931source = "registry+https://github.com/rust-lang/crates.io-index"
1932checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1933dependencies = [
1934 "form_urlencoded",
1935 "itoa",
1936 "ryu",
1937 "serde",
1938]
1939
1940[[package]]
1596name = "sha1" 1941name = "sha1"
1597version = "0.10.6" 1942version = "0.10.6"
1598source = "registry+https://github.com/rust-lang/crates.io-index" 1943source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1652,6 +1997,16 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
1652 1997
1653[[package]] 1998[[package]]
1654name = "socket2" 1999name = "socket2"
2000version = "0.5.10"
2001source = "registry+https://github.com/rust-lang/crates.io-index"
2002checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
2003dependencies = [
2004 "libc",
2005 "windows-sys 0.52.0",
2006]
2007
2008[[package]]
2009name = "socket2"
1655version = "0.6.1" 2010version = "0.6.1"
1656source = "registry+https://github.com/rust-lang/crates.io-index" 2011source = "registry+https://github.com/rust-lang/crates.io-index"
1657checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 2012checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
@@ -1690,6 +2045,12 @@ dependencies = [
1690] 2045]
1691 2046
1692[[package]] 2047[[package]]
2048name = "sync_wrapper"
2049version = "0.1.2"
2050source = "registry+https://github.com/rust-lang/crates.io-index"
2051checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
2052
2053[[package]]
1693name = "synstructure" 2054name = "synstructure"
1694version = "0.13.2" 2055version = "0.13.2"
1695source = "registry+https://github.com/rust-lang/crates.io-index" 2056source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1701,6 +2062,40 @@ dependencies = [
1701] 2062]
1702 2063
1703[[package]] 2064[[package]]
2065name = "system-configuration"
2066version = "0.5.1"
2067source = "registry+https://github.com/rust-lang/crates.io-index"
2068checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
2069dependencies = [
2070 "bitflags 1.3.2",
2071 "core-foundation",
2072 "system-configuration-sys",
2073]
2074
2075[[package]]
2076name = "system-configuration-sys"
2077version = "0.5.0"
2078source = "registry+https://github.com/rust-lang/crates.io-index"
2079checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
2080dependencies = [
2081 "core-foundation-sys",
2082 "libc",
2083]
2084
2085[[package]]
2086name = "tempfile"
2087version = "3.23.0"
2088source = "registry+https://github.com/rust-lang/crates.io-index"
2089checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
2090dependencies = [
2091 "fastrand",
2092 "getrandom 0.3.4",
2093 "once_cell",
2094 "rustix",
2095 "windows-sys 0.61.2",
2096]
2097
2098[[package]]
1704name = "thiserror" 2099name = "thiserror"
1705version = "1.0.69" 2100version = "1.0.69"
1706source = "registry+https://github.com/rust-lang/crates.io-index" 2101source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1786,7 +2181,7 @@ dependencies = [
1786 "parking_lot", 2181 "parking_lot",
1787 "pin-project-lite", 2182 "pin-project-lite",
1788 "signal-hook-registry", 2183 "signal-hook-registry",
1789 "socket2", 2184 "socket2 0.6.1",
1790 "tokio-macros", 2185 "tokio-macros",
1791 "windows-sys 0.61.2", 2186 "windows-sys 0.61.2",
1792] 2187]
@@ -1803,6 +2198,16 @@ dependencies = [
1803] 2198]
1804 2199
1805[[package]] 2200[[package]]
2201name = "tokio-native-tls"
2202version = "0.3.1"
2203source = "registry+https://github.com/rust-lang/crates.io-index"
2204checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
2205dependencies = [
2206 "native-tls",
2207 "tokio",
2208]
2209
2210[[package]]
1806name = "tokio-rustls" 2211name = "tokio-rustls"
1807version = "0.26.4" 2212version = "0.26.4"
1808source = "registry+https://github.com/rust-lang/crates.io-index" 2213source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1878,6 +2283,12 @@ dependencies = [
1878] 2283]
1879 2284
1880[[package]] 2285[[package]]
2286name = "tower-service"
2287version = "0.3.3"
2288source = "registry+https://github.com/rust-lang/crates.io-index"
2289checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
2290
2291[[package]]
1881name = "tracing" 2292name = "tracing"
1882version = "0.1.41" 2293version = "0.1.41"
1883source = "registry+https://github.com/rust-lang/crates.io-index" 2294source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1952,7 +2363,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
1952dependencies = [ 2363dependencies = [
1953 "bytes", 2364 "bytes",
1954 "data-encoding", 2365 "data-encoding",
1955 "http", 2366 "http 1.3.1",
1956 "httparse", 2367 "httparse",
1957 "log", 2368 "log",
1958 "rand 0.9.2", 2369 "rand 0.9.2",
@@ -2048,6 +2459,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2048checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2459checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
2049 2460
2050[[package]] 2461[[package]]
2462name = "vcpkg"
2463version = "0.2.15"
2464source = "registry+https://github.com/rust-lang/crates.io-index"
2465checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
2466
2467[[package]]
2051name = "version_check" 2468name = "version_check"
2052version = "0.9.5" 2469version = "0.9.5"
2053source = "registry+https://github.com/rust-lang/crates.io-index" 2470source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2224,6 +2641,15 @@ dependencies = [
2224 2641
2225[[package]] 2642[[package]]
2226name = "windows-sys" 2643name = "windows-sys"
2644version = "0.48.0"
2645source = "registry+https://github.com/rust-lang/crates.io-index"
2646checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
2647dependencies = [
2648 "windows-targets 0.48.5",
2649]
2650
2651[[package]]
2652name = "windows-sys"
2227version = "0.52.0" 2653version = "0.52.0"
2228source = "registry+https://github.com/rust-lang/crates.io-index" 2654source = "registry+https://github.com/rust-lang/crates.io-index"
2229checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2655checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -2251,6 +2677,21 @@ dependencies = [
2251 2677
2252[[package]] 2678[[package]]
2253name = "windows-targets" 2679name = "windows-targets"
2680version = "0.48.5"
2681source = "registry+https://github.com/rust-lang/crates.io-index"
2682checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
2683dependencies = [
2684 "windows_aarch64_gnullvm 0.48.5",
2685 "windows_aarch64_msvc 0.48.5",
2686 "windows_i686_gnu 0.48.5",
2687 "windows_i686_msvc 0.48.5",
2688 "windows_x86_64_gnu 0.48.5",
2689 "windows_x86_64_gnullvm 0.48.5",
2690 "windows_x86_64_msvc 0.48.5",
2691]
2692
2693[[package]]
2694name = "windows-targets"
2254version = "0.52.6" 2695version = "0.52.6"
2255source = "registry+https://github.com/rust-lang/crates.io-index" 2696source = "registry+https://github.com/rust-lang/crates.io-index"
2256checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2697checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -2284,6 +2725,12 @@ dependencies = [
2284 2725
2285[[package]] 2726[[package]]
2286name = "windows_aarch64_gnullvm" 2727name = "windows_aarch64_gnullvm"
2728version = "0.48.5"
2729source = "registry+https://github.com/rust-lang/crates.io-index"
2730checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
2731
2732[[package]]
2733name = "windows_aarch64_gnullvm"
2287version = "0.52.6" 2734version = "0.52.6"
2288source = "registry+https://github.com/rust-lang/crates.io-index" 2735source = "registry+https://github.com/rust-lang/crates.io-index"
2289checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2736checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -2296,6 +2743,12 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
2296 2743
2297[[package]] 2744[[package]]
2298name = "windows_aarch64_msvc" 2745name = "windows_aarch64_msvc"
2746version = "0.48.5"
2747source = "registry+https://github.com/rust-lang/crates.io-index"
2748checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
2749
2750[[package]]
2751name = "windows_aarch64_msvc"
2299version = "0.52.6" 2752version = "0.52.6"
2300source = "registry+https://github.com/rust-lang/crates.io-index" 2753source = "registry+https://github.com/rust-lang/crates.io-index"
2301checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2754checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -2308,6 +2761,12 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
2308 2761
2309[[package]] 2762[[package]]
2310name = "windows_i686_gnu" 2763name = "windows_i686_gnu"
2764version = "0.48.5"
2765source = "registry+https://github.com/rust-lang/crates.io-index"
2766checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
2767
2768[[package]]
2769name = "windows_i686_gnu"
2311version = "0.52.6" 2770version = "0.52.6"
2312source = "registry+https://github.com/rust-lang/crates.io-index" 2771source = "registry+https://github.com/rust-lang/crates.io-index"
2313checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2772checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -2332,6 +2791,12 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
2332 2791
2333[[package]] 2792[[package]]
2334name = "windows_i686_msvc" 2793name = "windows_i686_msvc"
2794version = "0.48.5"
2795source = "registry+https://github.com/rust-lang/crates.io-index"
2796checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
2797
2798[[package]]
2799name = "windows_i686_msvc"
2335version = "0.52.6" 2800version = "0.52.6"
2336source = "registry+https://github.com/rust-lang/crates.io-index" 2801source = "registry+https://github.com/rust-lang/crates.io-index"
2337checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2802checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -2344,6 +2809,12 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
2344 2809
2345[[package]] 2810[[package]]
2346name = "windows_x86_64_gnu" 2811name = "windows_x86_64_gnu"
2812version = "0.48.5"
2813source = "registry+https://github.com/rust-lang/crates.io-index"
2814checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
2815
2816[[package]]
2817name = "windows_x86_64_gnu"
2347version = "0.52.6" 2818version = "0.52.6"
2348source = "registry+https://github.com/rust-lang/crates.io-index" 2819source = "registry+https://github.com/rust-lang/crates.io-index"
2349checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2820checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -2356,6 +2827,12 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
2356 2827
2357[[package]] 2828[[package]]
2358name = "windows_x86_64_gnullvm" 2829name = "windows_x86_64_gnullvm"
2830version = "0.48.5"
2831source = "registry+https://github.com/rust-lang/crates.io-index"
2832checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
2833
2834[[package]]
2835name = "windows_x86_64_gnullvm"
2359version = "0.52.6" 2836version = "0.52.6"
2360source = "registry+https://github.com/rust-lang/crates.io-index" 2837source = "registry+https://github.com/rust-lang/crates.io-index"
2361checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2838checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -2368,6 +2845,12 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
2368 2845
2369[[package]] 2846[[package]]
2370name = "windows_x86_64_msvc" 2847name = "windows_x86_64_msvc"
2848version = "0.48.5"
2849source = "registry+https://github.com/rust-lang/crates.io-index"
2850checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
2851
2852[[package]]
2853name = "windows_x86_64_msvc"
2371version = "0.52.6" 2854version = "0.52.6"
2372source = "registry+https://github.com/rust-lang/crates.io-index" 2855source = "registry+https://github.com/rust-lang/crates.io-index"
2373checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2856checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -2379,6 +2862,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2379checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2862checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
2380 2863
2381[[package]] 2864[[package]]
2865name = "winreg"
2866version = "0.50.0"
2867source = "registry+https://github.com/rust-lang/crates.io-index"
2868checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
2869dependencies = [
2870 "cfg-if",
2871 "windows-sys 0.48.0",
2872]
2873
2874[[package]]
2382name = "wit-bindgen" 2875name = "wit-bindgen"
2383version = "0.46.0" 2876version = "0.46.0"
2384source = "registry+https://github.com/rust-lang/crates.io-index" 2877source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/grasp-audit/Cargo.toml b/grasp-audit/Cargo.toml
index 278c855..0bc008a 100644
--- a/grasp-audit/Cargo.toml
+++ b/grasp-audit/Cargo.toml
@@ -33,6 +33,10 @@ clap = { version = "4", features = ["derive"] }
33uuid = { version = "1", features = ["v4"] } 33uuid = { version = "1", features = ["v4"] }
34chrono = "0.4" 34chrono = "0.4"
35 35
36# HTTP Client
37reqwest = { version = "0.11", features = ["json"] }
38regex = "1"
39
36# Logging 40# Logging
37tracing = "0.1" 41tracing = "0.1"
38tracing-subscriber = { version = "0.3", features = ["env-filter"] } 42tracing-subscriber = { version = "0.3", features = ["env-filter"] }
diff --git a/grasp-audit/src/client.rs b/grasp-audit/src/client.rs
index 019f4cb..35aaccd 100644
--- a/grasp-audit/src/client.rs
+++ b/grasp-audit/src/client.rs
@@ -84,6 +84,25 @@ impl AuditClient {
84 self.keys.public_key() 84 self.keys.public_key()
85 } 85 }
86 86
87 /// Get the relay URL
88 pub async fn relay_url(&self) -> Result<String> {
89 let relays = self.client.relays().await;
90 let relay = relays.values().next()
91 .ok_or_else(|| anyhow!("No relays configured"))?;
92 Ok(relay.url().to_string())
93 }
94
95 /// Convert WebSocket URL to HTTP(S) URL for NIP-11 requests
96 pub fn ws_to_http_url(ws_url: &str) -> Result<String> {
97 if ws_url.starts_with("ws://") {
98 Ok(ws_url.replace("ws://", "http://"))
99 } else if ws_url.starts_with("wss://") {
100 Ok(ws_url.replace("wss://", "https://"))
101 } else {
102 Err(anyhow!("Invalid WebSocket URL: {}", ws_url))
103 }
104 }
105
87 /// Check if connected to relay 106 /// Check if connected to relay
88 pub async fn is_connected(&self) -> bool { 107 pub async fn is_connected(&self) -> bool {
89 // Check if we have any connected relays 108 // Check if we have any connected relays
diff --git a/grasp-audit/src/specs/grasp01/nip11_document.rs b/grasp-audit/src/specs/grasp01/nip11_document.rs
index be04777..bb864f2 100644
--- a/grasp-audit/src/specs/grasp01/nip11_document.rs
+++ b/grasp-audit/src/specs/grasp01/nip11_document.rs
@@ -34,25 +34,53 @@ impl Nip11DocumentTests {
34 /// 34 ///
35 /// Spec: Line 11 of ../grasp/01.md 35 /// Spec: Line 11 of ../grasp/01.md
36 /// Requirement: MUST serve NIP-11 document 36 /// Requirement: MUST serve NIP-11 document
37 async fn test_nip11_document_exists(_client: &AuditClient) -> TestResult { 37 pub async fn test_nip11_document_exists(client: &AuditClient) -> TestResult {
38 TestResult::new( 38 TestResult::new(
39 "nip11_document_exists", 39 "nip11_document_exists",
40 "GRASP-01:nostr-relay:11", 40 "GRASP-01:nostr-relay:11",
41 "Serve NIP-11 relay information document", 41 "Serve NIP-11 relay information document",
42 ) 42 )
43 .run(|| async { 43 .run(|| async {
44 // TODO: Implementation
45 // 1. Extract HTTP(S) URL from client's WebSocket URL 44 // 1. Extract HTTP(S) URL from client's WebSocket URL
46 // - ws://localhost:8081 -> http://localhost:8081 45 let ws_url = client.relay_url().await
47 // - wss://relay.example.com -> https://relay.example.com 46 .map_err(|e| format!("Failed to get relay URL: {}", e))?;
48 // 2. HTTP GET to base URL with header: 47 let http_url = AuditClient::ws_to_http_url(&ws_url)
49 // - Accept: application/nostr+json 48 .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
49
50 // 2. HTTP GET to base URL with Accept: application/nostr+json header
51 let http_client = reqwest::Client::new();
52 let response = http_client
53 .get(&http_url)
54 .header("Accept", "application/nostr+json")
55 .send()
56 .await
57 .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
58
50 // 3. Verify 200 OK response 59 // 3. Verify 200 OK response
60 if !response.status().is_success() {
61 return Err(format!(
62 "Expected 200 OK, got {} {}",
63 response.status().as_u16(),
64 response.status().canonical_reason().unwrap_or("Unknown")
65 ));
66 }
67
51 // 4. Verify response is valid JSON 68 // 4. Verify response is valid JSON
52 // 5. Parse as NIP-11 document 69 let json_text = response.text().await
53 // 6. Verify has required fields (name, description, etc.) 70 .map_err(|e| format!("Failed to read response body: {}", e))?;
71
72 let doc: serde_json::Value = serde_json::from_str(&json_text)
73 .map_err(|e| format!("Response is not valid JSON: {}", e))?;
54 74
55 Err("Not implemented yet".to_string()) 75 // 5. Verify has required NIP-11 fields
76 let required_fields = ["name", "description", "software", "version"];
77 for field in &required_fields {
78 if !doc.get(field).is_some() {
79 return Err(format!("Missing required NIP-11 field: {}", field));
80 }
81 }
82
83 Ok(())
56 }) 84 })
57 .await 85 .await
58 } 86 }
@@ -61,22 +89,68 @@ impl Nip11DocumentTests {
61 /// 89 ///
62 /// Spec: Line 12 of ../grasp/01.md 90 /// Spec: Line 12 of ../grasp/01.md
63 /// Requirement: MUST list supported GRASPs as string array 91 /// Requirement: MUST list supported GRASPs as string array
64 async fn test_nip11_supported_grasps_field(_client: &AuditClient) -> TestResult { 92 pub async fn test_nip11_supported_grasps_field(client: &AuditClient) -> TestResult {
65 TestResult::new( 93 TestResult::new(
66 "nip11_supported_grasps_field", 94 "nip11_supported_grasps_field",
67 "GRASP-01:nostr-relay:12", 95 "GRASP-01:nostr-relay:12",
68 "NIP-11 document includes supported_grasps field with GRASP-01", 96 "NIP-11 document includes supported_grasps field with GRASP-01",
69 ) 97 )
70 .run(|| async { 98 .run(|| async {
71 // TODO: Implementation 99 // 1. Fetch NIP-11 document
72 // 1. Fetch NIP-11 document (same as above) 100 let ws_url = client.relay_url().await
101 .map_err(|e| format!("Failed to get relay URL: {}", e))?;
102 let http_url = AuditClient::ws_to_http_url(&ws_url)
103 .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
104
105 let http_client = reqwest::Client::new();
106 let response = http_client
107 .get(&http_url)
108 .header("Accept", "application/nostr+json")
109 .send()
110 .await
111 .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
112
113 let json_text = response.text().await
114 .map_err(|e| format!("Failed to read response body: {}", e))?;
115
116 let doc: serde_json::Value = serde_json::from_str(&json_text)
117 .map_err(|e| format!("Response is not valid JSON: {}", e))?;
118
73 // 2. Verify `supported_grasps` field exists 119 // 2. Verify `supported_grasps` field exists
74 // 3. Verify it's a JSON array of strings 120 let supported_grasps = doc.get("supported_grasps")
121 .ok_or_else(|| "Missing required field: supported_grasps".to_string())?;
122
123 // 3. Verify it's a JSON array
124 let grasps_array = supported_grasps.as_array()
125 .ok_or_else(|| "supported_grasps must be an array".to_string())?;
126
75 // 4. Verify array includes "GRASP-01" 127 // 4. Verify array includes "GRASP-01"
76 // 5. Verify format: each entry matches pattern "GRASP-\d{2}" 128 let grasp_strings: Vec<String> = grasps_array
77 // 6. Document other GRASPs found (for info) 129 .iter()
130 .filter_map(|v| v.as_str().map(|s| s.to_string()))
131 .collect();
132
133 if !grasp_strings.contains(&"GRASP-01".to_string()) {
134 return Err(format!(
135 "supported_grasps must include 'GRASP-01', found: {:?}",
136 grasp_strings
137 ));
138 }
139
140 // 5. Verify format: each entry should match pattern "GRASP-\d{2}"
141 let grasp_pattern = regex::Regex::new(r"^GRASP-\d{2}$")
142 .map_err(|e| format!("Failed to compile regex: {}", e))?;
143
144 for grasp in &grasp_strings {
145 if !grasp_pattern.is_match(grasp) {
146 return Err(format!(
147 "Invalid GRASP format: '{}' (expected GRASP-XX where XX is two digits)",
148 grasp
149 ));
150 }
151 }
78 152
79 Err("Not implemented yet".to_string()) 153 Ok(())
80 }) 154 })
81 .await 155 .await
82 } 156 }
@@ -85,23 +159,47 @@ impl Nip11DocumentTests {
85 /// 159 ///
86 /// Spec: Line 13 of ../grasp/01.md 160 /// Spec: Line 13 of ../grasp/01.md
87 /// Requirement: MUST list repository acceptance criteria 161 /// Requirement: MUST list repository acceptance criteria
88 async fn test_nip11_repo_acceptance_criteria_field(_client: &AuditClient) -> TestResult { 162 pub async fn test_nip11_repo_acceptance_criteria_field(client: &AuditClient) -> TestResult {
89 TestResult::new( 163 TestResult::new(
90 "nip11_repo_acceptance_criteria_field", 164 "nip11_repo_acceptance_criteria_field",
91 "GRASP-01:nostr-relay:13", 165 "GRASP-01:nostr-relay:13",
92 "NIP-11 document includes repo_acceptance_criteria field", 166 "NIP-11 document includes repo_acceptance_criteria field",
93 ) 167 )
94 .run(|| async { 168 .run(|| async {
95 // TODO: Implementation
96 // 1. Fetch NIP-11 document 169 // 1. Fetch NIP-11 document
170 let ws_url = client.relay_url().await
171 .map_err(|e| format!("Failed to get relay URL: {}", e))?;
172 let http_url = AuditClient::ws_to_http_url(&ws_url)
173 .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
174
175 let http_client = reqwest::Client::new();
176 let response = http_client
177 .get(&http_url)
178 .header("Accept", "application/nostr+json")
179 .send()
180 .await
181 .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
182
183 let json_text = response.text().await
184 .map_err(|e| format!("Failed to read response body: {}", e))?;
185
186 let doc: serde_json::Value = serde_json::from_str(&json_text)
187 .map_err(|e| format!("Response is not valid JSON: {}", e))?;
188
97 // 2. Verify `repo_acceptance_criteria` field exists 189 // 2. Verify `repo_acceptance_criteria` field exists
98 // 3. Verify it's a string (human-readable) 190 let criteria = doc.get("repo_acceptance_criteria")
191 .ok_or_else(|| "Missing required field: repo_acceptance_criteria".to_string())?;
192
193 // 3. Verify it's a string
194 let criteria_str = criteria.as_str()
195 .ok_or_else(|| "repo_acceptance_criteria must be a string".to_string())?;
196
99 // 4. Verify non-empty 197 // 4. Verify non-empty
100 // 5. Document the criteria (for info) 198 if criteria_str.trim().is_empty() {
101 // Examples: "Must list this relay in clone and relays tags" 199 return Err("repo_acceptance_criteria must not be empty".to_string());
102 // "Pre-payment required via Lightning invoice" 200 }
103 201
104 Err("Not implemented yet".to_string()) 202 Ok(())
105 }) 203 })
106 .await 204 .await
107 } 205 }
@@ -110,24 +208,47 @@ impl Nip11DocumentTests {
110 /// 208 ///
111 /// Spec: Line 14 of ../grasp/01.md 209 /// Spec: Line 14 of ../grasp/01.md
112 /// Requirement: MUST include curation if curated, omit otherwise 210 /// Requirement: MUST include curation if curated, omit otherwise
113 async fn test_nip11_curation_field(_client: &AuditClient) -> TestResult { 211 pub async fn test_nip11_curation_field(client: &AuditClient) -> TestResult {
114 TestResult::new( 212 TestResult::new(
115 "nip11_curation_field", 213 "nip11_curation_field",
116 "GRASP-01:nostr-relay:14", 214 "GRASP-01:nostr-relay:14",
117 "NIP-11 curation field present if curated, absent otherwise", 215 "NIP-11 curation field present if curated, absent otherwise",
118 ) 216 )
119 .run(|| async { 217 .run(|| async {
120 // TODO: Implementation
121 // 1. Fetch NIP-11 document 218 // 1. Fetch NIP-11 document
219 let ws_url = client.relay_url().await
220 .map_err(|e| format!("Failed to get relay URL: {}", e))?;
221 let http_url = AuditClient::ws_to_http_url(&ws_url)
222 .map_err(|e| format!("Failed to convert WebSocket URL to HTTP: {}", e))?;
223
224 let http_client = reqwest::Client::new();
225 let response = http_client
226 .get(&http_url)
227 .header("Accept", "application/nostr+json")
228 .send()
229 .await
230 .map_err(|e| format!("Failed to fetch NIP-11 document: {}", e))?;
231
232 let json_text = response.text().await
233 .map_err(|e| format!("Failed to read response body: {}", e))?;
234
235 let doc: serde_json::Value = serde_json::from_str(&json_text)
236 .map_err(|e| format!("Response is not valid JSON: {}", e))?;
237
122 // 2. Check if `curation` field exists 238 // 2. Check if `curation` field exists
123 // 3. If present: 239 if let Some(curation) = doc.get("curation") {
124 // - Verify it's a non-empty string 240 // 3. If present: verify it's a non-empty string
125 // - Document the curation policy 241 let curation_str = curation.as_str()
126 // 4. If absent: 242 .ok_or_else(|| "curation field must be a string when present".to_string())?;
127 // - Document that no curation beyond SPAM prevention 243
128 // 5. Both cases are valid per spec 244 if curation_str.trim().is_empty() {
129 245 return Err("curation field must not be empty when present".to_string());
130 Err("Not implemented yet".to_string()) 246 }
247 }
248 // 4. If absent: both cases are valid per spec
249
250 // 5. Both cases are valid - test passes
251 Ok(())
131 }) 252 })
132 .await 253 .await
133 } 254 }
@@ -163,4 +284,4 @@ mod tests {
163 // Don't assert all passed yet - tests not implemented 284 // Don't assert all passed yet - tests not implemented
164 // assert!(results.all_passed(), "Some GRASP-01 NIP-11 document tests failed"); 285 // assert!(results.all_passed(), "Some GRASP-01 NIP-11 document tests failed");
165 } 286 }
166} 287} \ No newline at end of file
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 4690790..7c0e7bb 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -2,6 +2,7 @@
2/// 2///
3/// Provides hyper HTTP server with WebSocket upgrade support for the Nostr relay. 3/// Provides hyper HTTP server with WebSocket upgrade support for the Nostr relay.
4pub mod landing; 4pub mod landing;
5pub mod nip11;
5 6
6use std::future::Future; 7use std::future::Future;
7use std::net::SocketAddr; 8use std::net::SocketAddr;
@@ -46,6 +47,32 @@ impl Service<Request<Incoming>> for HttpService {
46 fn call(&self, req: Request<Incoming>) -> Self::Future { 47 fn call(&self, req: Request<Incoming>) -> Self::Future {
47 let base = Response::builder().header("server", "ngit-grasp"); 48 let base = Response::builder().header("server", "ngit-grasp");
48 49
50 // Check for NIP-11 relay information request (Accept: application/nostr+json)
51 if let Some(accept) = req.headers().get("accept") {
52 if accept
53 .to_str()
54 .map(|s| s.contains("application/nostr+json"))
55 .unwrap_or(false)
56 {
57 let doc = nip11::RelayInformationDocument::from_config(&self.config);
58 let json = doc.to_json().unwrap_or_else(|e| {
59 tracing::error!("Failed to serialize NIP-11 document: {}", e);
60 "{}".to_string()
61 });
62
63 tracing::debug!("Serving NIP-11 relay information document to {}", self.remote);
64
65 return Box::pin(async move {
66 Ok(base
67 .status(200)
68 .header("content-type", "application/nostr+json")
69 .header("access-control-allow-origin", "*")
70 .body(json)
71 .unwrap())
72 });
73 }
74 }
75
49 // Check if this is a WebSocket upgrade request 76 // Check if this is a WebSocket upgrade request
50 if let (Some(c), Some(w)) = ( 77 if let (Some(c), Some(w)) = (
51 req.headers().get("connection"), 78 req.headers().get("connection"),
diff --git a/src/http/nip11.rs b/src/http/nip11.rs
new file mode 100644
index 0000000..a93ee5f
--- /dev/null
+++ b/src/http/nip11.rs
@@ -0,0 +1,146 @@
1/// NIP-11 Relay Information Document
2///
3/// Implements NIP-11 relay information endpoint with GRASP-01 extensions.
4/// See: https://github.com/nostr-protocol/nips/blob/master/11.md
5
6use serde::{Deserialize, Serialize};
7use crate::config::Config;
8
9/// NIP-11 Relay Information Document
10///
11/// This structure represents the relay metadata served at the HTTP(S) endpoint
12/// when the client sends `Accept: application/nostr+json` header.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct RelayInformationDocument {
15 /// Relay name
16 pub name: String,
17
18 /// Relay description
19 pub description: String,
20
21 /// Relay owner's public key (hex format)
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub pubkey: Option<String>,
24
25 /// Contact information for relay admin
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub contact: Option<String>,
28
29 /// List of NIPs supported by this relay
30 pub supported_nips: Vec<u16>,
31
32 /// Relay software identifier
33 pub software: String,
34
35 /// Software version
36 pub version: String,
37
38 // GRASP-01 Extensions (lines 11-14 of GRASP-01 spec)
39
40 /// List of supported GRASPs (e.g., ["GRASP-01"])
41 /// Required by GRASP-01 specification line 12
42 pub supported_grasps: Vec<String>,
43
44 /// Repository acceptance criteria description
45 /// Required by GRASP-01 specification line 13
46 pub repo_acceptance_criteria: String,
47
48 /// Curation policy (present if curated, absent otherwise)
49 /// Optional per GRASP-01 specification line 14
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub curation: Option<String>,
52}
53
54impl RelayInformationDocument {
55 /// Create NIP-11 relay information document from configuration
56 pub fn from_config(config: &Config) -> Self {
57 Self {
58 name: config.relay_name.clone(),
59 description: config.relay_description.clone(),
60 pubkey: Some(config.owner_npub.clone()),
61 contact: None, // Could be added to config if needed
62 supported_nips: vec![
63 1, // NIP-01: Basic protocol flow
64 11, // NIP-11: Relay information document (this!)
65 34, // NIP-34: Git repository announcements
66 ],
67 software: env!("CARGO_PKG_NAME").to_string(),
68 version: env!("CARGO_PKG_VERSION").to_string(),
69
70 // GRASP-01 Extensions
71 supported_grasps: vec!["GRASP-01".to_string()],
72 repo_acceptance_criteria: format!(
73 "Repositories must list this relay ({}) in both 'clone' and 'relays' tags of kind 30617 announcements. \
74 All other events must reference accepted repositories or accepted events.",
75 config.domain
76 ),
77 curation: None, // Not a curated relay - only SPAM prevention via GRASP-01 policy
78 }
79 }
80
81 /// Serialize to JSON string
82 pub fn to_json(&self) -> Result<String, serde_json::Error> {
83 serde_json::to_string_pretty(self)
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_relay_information_document_structure() {
93 let config = Config {
94 domain: "relay.example.com".to_string(),
95 owner_npub: "npub1test".to_string(),
96 relay_name: "Test Relay".to_string(),
97 relay_description: "A test relay".to_string(),
98 git_data_path: "./data/git".to_string(),
99 relay_data_path: "./data/relay".to_string(),
100 bind_address: "127.0.0.1:8080".to_string(),
101 database_backend: crate::config::DatabaseBackend::Memory,
102 };
103
104 let doc = RelayInformationDocument::from_config(&config);
105
106 assert_eq!(doc.name, "Test Relay");
107 assert_eq!(doc.description, "A test relay");
108 assert_eq!(doc.pubkey, Some("npub1test".to_string()));
109 assert!(doc.supported_nips.contains(&1));
110 assert!(doc.supported_nips.contains(&11));
111 assert!(doc.supported_nips.contains(&34));
112 assert_eq!(doc.supported_grasps, vec!["GRASP-01"]);
113 assert!(doc.repo_acceptance_criteria.contains("relay.example.com"));
114 assert!(doc.curation.is_none());
115 }
116
117 #[test]
118 fn test_relay_information_document_json() {
119 let config = Config {
120 domain: "relay.example.com".to_string(),
121 owner_npub: "npub1test".to_string(),
122 relay_name: "Test Relay".to_string(),
123 relay_description: "A test relay".to_string(),
124 git_data_path: "./data/git".to_string(),
125 relay_data_path: "./data/relay".to_string(),
126 bind_address: "127.0.0.1:8080".to_string(),
127 database_backend: crate::config::DatabaseBackend::Memory,
128 };
129
130 let doc = RelayInformationDocument::from_config(&config);
131 let json = doc.to_json().expect("Failed to serialize to JSON");
132
133 // Verify JSON contains expected fields
134 assert!(json.contains("\"name\""));
135 assert!(json.contains("\"description\""));
136 assert!(json.contains("\"supported_nips\""));
137 assert!(json.contains("\"supported_grasps\""));
138 assert!(json.contains("\"repo_acceptance_criteria\""));
139 assert!(json.contains("GRASP-01"));
140
141 // Verify it's valid JSON by parsing
142 let parsed: serde_json::Value = serde_json::from_str(&json).expect("Invalid JSON");
143 assert_eq!(parsed["name"], "Test Relay");
144 assert_eq!(parsed["supported_grasps"][0], "GRASP-01");
145 }
146} \ No newline at end of file
diff --git a/tests/nip11_document.rs b/tests/nip11_document.rs
new file mode 100644
index 0000000..da8e9ce
--- /dev/null
+++ b/tests/nip11_document.rs
@@ -0,0 +1,62 @@
1//! GRASP-01 NIP-11 Document Integration Tests
2//!
3//! Tests ngit-grasp relay's implementation of GRASP-01 NIP-11 relay information requirements.
4//! Uses grasp-audit library to avoid code duplication.
5//!
6//! # Test Strategy
7//!
8//! - Each test runs in complete isolation with its own fresh relay instance
9//! - Uses macro to eliminate boilerplate while maintaining test isolation
10//! - Calls individual test methods from grasp-audit for minimal duplication
11//!
12//! # Running Tests
13//!
14//! ```bash
15//! # Run all GRASP-01 NIP-11 tests
16//! cargo test --test nip11_document
17//!
18//! # Run specific test
19//! cargo test --test nip11_document test_nip11_document_exists
20//!
21//! # With output
22//! cargo test --test nip11_document -- --nocapture
23//! ```
24
25mod common;
26
27use common::TestRelay;
28use grasp_audit::*;
29
30/// Macro to generate isolated integration tests
31///
32/// Each test runs with its own fresh relay instance to ensure complete isolation.
33/// This eliminates rate-limiting issues and ensures tests don't interfere with each other.
34macro_rules! isolated_test {
35 ($test_name:ident) => {
36 #[tokio::test]
37 async fn $test_name() {
38 let relay = TestRelay::start().await;
39 let config = AuditConfig::ci();
40 let client = AuditClient::new(relay.url(), config)
41 .await
42 .expect("Failed to create audit client");
43
44 let result = specs::Nip11DocumentTests::$test_name(&client).await;
45
46 relay.stop().await;
47
48 assert!(
49 result.passed,
50 "{} failed: {}",
51 stringify!($test_name),
52 result.error.as_deref().unwrap_or("unknown error")
53 );
54 }
55 };
56}
57
58// Generate isolated tests for all GRASP-01 NIP-11 document tests
59isolated_test!(test_nip11_document_exists);
60isolated_test!(test_nip11_supported_grasps_field);
61isolated_test!(test_nip11_repo_acceptance_criteria_field);
62isolated_test!(test_nip11_curation_field); \ No newline at end of file