Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add web-based authentication
Sebastian Martinez committed 3 years ago
commit f07a875ce1e672b45f7401a38056b598d17d1935
parent deeb160c2d0af1679c68f16a683e9aa0e76fe637
14 files changed +273 -657
modified Cargo.lock
@@ -30,15 +30,6 @@ dependencies = [
]

[[package]]
-
name = "aho-corasick"
-
version = "0.7.20"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
-
dependencies = [
-
 "memchr",
-
]
-

-
[[package]]
name = "amplify"
version = "4.0.0-beta.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -95,12 +86,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"

[[package]]
-
name = "arrayvec"
-
version = "0.7.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
-

-
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -129,18 +114,6 @@ dependencies = [
]

[[package]]
-
name = "auto_impl"
-
version = "1.0.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33"
-
dependencies = [
-
 "proc-macro-error",
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -260,18 +233,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
-
name = "bitvec"
-
version = "1.0.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
-
dependencies = [
-
 "funty",
-
 "radium",
-
 "tap",
-
 "wyz",
-
]
-

-
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -315,12 +276,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"

[[package]]
-
name = "byte-slice-cast"
-
version = "1.2.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
-

-
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -331,9 +286,6 @@ name = "bytes"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
-
dependencies = [
-
 "serde",
-
]

[[package]]
name = "cc"
@@ -501,12 +453,6 @@ dependencies = [
]

[[package]]
-
name = "crunchy"
-
version = "0.2.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
-

-
[[package]]
name = "crypto-bigint"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -686,17 +632,6 @@ dependencies = [
]

[[package]]
-
name = "derive_more"
-
version = "0.99.17"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
-
dependencies = [
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "dialoguer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -838,80 +773,6 @@ dependencies = [
]

[[package]]
-
name = "ethabi"
-
version = "18.0.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898"
-
dependencies = [
-
 "ethereum-types",
-
 "hex",
-
 "once_cell",
-
 "regex",
-
 "serde",
-
 "serde_json",
-
 "sha3",
-
 "thiserror",
-
 "uint",
-
]
-

-
[[package]]
-
name = "ethbloom"
-
version = "0.13.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60"
-
dependencies = [
-
 "crunchy",
-
 "fixed-hash",
-
 "impl-codec",
-
 "impl-rlp",
-
 "impl-serde",
-
 "scale-info",
-
 "tiny-keccak",
-
]
-

-
[[package]]
-
name = "ethereum-types"
-
version = "0.14.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee"
-
dependencies = [
-
 "ethbloom",
-
 "fixed-hash",
-
 "impl-codec",
-
 "impl-rlp",
-
 "impl-serde",
-
 "primitive-types",
-
 "scale-info",
-
 "uint",
-
]
-

-
[[package]]
-
name = "ethers-core"
-
version = "1.0.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ade3e9c97727343984e1ceada4fdab11142d2ee3472d2c67027d56b1251d4f15"
-
dependencies = [
-
 "arrayvec",
-
 "bytes",
-
 "chrono",
-
 "elliptic-curve",
-
 "ethabi",
-
 "generic-array",
-
 "hex",
-
 "k256",
-
 "open-fastrlp",
-
 "rand 0.8.5",
-
 "rlp",
-
 "rlp-derive",
-
 "serde",
-
 "serde_json",
-
 "strum",
-
 "thiserror",
-
 "tiny-keccak",
-
 "unicode-xid",
-
]
-

-
[[package]]
name = "fastrand"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -943,18 +804,6 @@ dependencies = [
]

[[package]]
-
name = "fixed-hash"
-
version = "0.8.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
-
dependencies = [
-
 "byteorder",
-
 "rand 0.8.5",
-
 "rustc-hex",
-
 "static_assertions",
-
]
-

-
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -980,12 +829,6 @@ dependencies = [
]

[[package]]
-
name = "funty"
-
version = "2.0.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
-

-
[[package]]
name = "futures-channel"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1181,12 +1024,6 @@ dependencies = [
]

[[package]]
-
name = "heck"
-
version = "0.4.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
-

-
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1205,12 +1042,6 @@ dependencies = [
]

[[package]]
-
name = "hex"
-
version = "0.4.3"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-

-
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1318,44 +1149,6 @@ dependencies = [
]

[[package]]
-
name = "impl-codec"
-
version = "0.6.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f"
-
dependencies = [
-
 "parity-scale-codec",
-
]
-

-
[[package]]
-
name = "impl-rlp"
-
version = "0.3.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808"
-
dependencies = [
-
 "rlp",
-
]
-

-
[[package]]
-
name = "impl-serde"
-
version = "0.4.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd"
-
dependencies = [
-
 "serde",
-
]
-

-
[[package]]
-
name = "impl-trait-for-tuples"
-
version = "0.2.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb"
-
dependencies = [
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1418,16 +1211,6 @@ dependencies = [
]

[[package]]
-
name = "iri-string"
-
version = "0.6.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "6d0586ad318a04c73acdbad33f67969519b5452c80770c4c72059a686da48a7e"
-
dependencies = [
-
 "memchr",
-
 "serde",
-
]
-

-
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1475,19 +1258,6 @@ dependencies = [
]

[[package]]
-
name = "k256"
-
version = "0.11.6"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
-
dependencies = [
-
 "cfg-if",
-
 "ecdsa",
-
 "elliptic-curve",
-
 "sha2 0.10.6",
-
 "sha3",
-
]
-

-
[[package]]
name = "keccak"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1685,15 +1455,6 @@ dependencies = [
]

[[package]]
-
name = "nom8"
-
version = "0.2.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
-
dependencies = [
-
 "memchr",
-
]
-

-
[[package]]
name = "nonempty"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1812,31 +1573,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"

[[package]]
-
name = "open-fastrlp"
-
version = "0.1.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce"
-
dependencies = [
-
 "arrayvec",
-
 "auto_impl",
-
 "bytes",
-
 "ethereum-types",
-
 "open-fastrlp-derive",
-
]
-

-
[[package]]
-
name = "open-fastrlp-derive"
-
version = "0.1.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c"
-
dependencies = [
-
 "bytes",
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "output_vt100"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1874,32 +1610,6 @@ dependencies = [
]

[[package]]
-
name = "parity-scale-codec"
-
version = "3.2.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e7ab01d0f889e957861bc65888d5ccbe82c158d0270136ba46820d43837cdf72"
-
dependencies = [
-
 "arrayvec",
-
 "bitvec",
-
 "byte-slice-cast",
-
 "impl-trait-for-tuples",
-
 "parity-scale-codec-derive",
-
 "serde",
-
]
-

-
[[package]]
-
name = "parity-scale-codec-derive"
-
version = "3.1.4"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b"
-
dependencies = [
-
 "proc-macro-crate",
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2022,30 +1732,6 @@ dependencies = [
]

[[package]]
-
name = "primitive-types"
-
version = "0.12.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66"
-
dependencies = [
-
 "fixed-hash",
-
 "impl-codec",
-
 "impl-rlp",
-
 "impl-serde",
-
 "scale-info",
-
 "uint",
-
]
-

-
[[package]]
-
name = "proc-macro-crate"
-
version = "1.3.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
-
dependencies = [
-
 "once_cell",
-
 "toml_edit",
-
]
-

-
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2162,6 +1848,7 @@ dependencies = [
 "tempfile",
 "thiserror",
 "timeago",
+
 "ureq",
 "zeroize",
]

@@ -2252,7 +1939,6 @@ dependencies = [
 "axum",
 "axum-server",
 "chrono",
-
 "ethers-core",
 "fastrand",
 "flate2",
 "hyper",
@@ -2263,7 +1949,6 @@ dependencies = [
 "radicle-surf",
 "serde",
 "serde_json",
-
 "siwe",
 "tempfile",
 "thiserror",
 "time 0.3.17",
@@ -2361,12 +2046,6 @@ dependencies = [
]

[[package]]
-
name = "radium"
-
version = "0.7.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-

-
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2452,8 +2131,6 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
-
 "aho-corasick",
-
 "memchr",
 "regex-syntax",
]

@@ -2493,27 +2170,6 @@ dependencies = [
]

[[package]]
-
name = "rlp"
-
version = "0.5.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec"
-
dependencies = [
-
 "bytes",
-
 "rustc-hex",
-
]
-

-
[[package]]
-
name = "rlp-derive"
-
version = "0.1.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
-
dependencies = [
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "rsa"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2535,12 +2191,6 @@ dependencies = [
]

[[package]]
-
name = "rustc-hex"
-
version = "2.1.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
-

-
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2576,30 +2226,6 @@ dependencies = [
]

[[package]]
-
name = "scale-info"
-
version = "2.3.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608"
-
dependencies = [
-
 "cfg-if",
-
 "derive_more",
-
 "parity-scale-codec",
-
 "scale-info-derive",
-
]
-

-
[[package]]
-
name = "scale-info-derive"
-
version = "2.3.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c"
-
dependencies = [
-
 "proc-macro-crate",
-
 "proc-macro2",
-
 "quote",
-
 "syn",
-
]
-

-
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2785,22 +2411,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"

[[package]]
-
name = "siwe"
-
version = "0.5.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "ec4cc2eafb2354c1aeaeac5f53b7726ca4ea5ad16314a0b5ebacb6676f599c2b"
-
dependencies = [
-
 "hex",
-
 "http",
-
 "iri-string",
-
 "k256",
-
 "rand 0.8.5",
-
 "sha3",
-
 "thiserror",
-
 "time 0.3.17",
-
]
-

-
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2933,34 +2543,6 @@ dependencies = [
]

[[package]]
-
name = "static_assertions"
-
version = "1.1.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-

-
[[package]]
-
name = "strum"
-
version = "0.24.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
-
dependencies = [
-
 "strum_macros",
-
]
-

-
[[package]]
-
name = "strum_macros"
-
version = "0.24.3"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
-
dependencies = [
-
 "heck",
-
 "proc-macro2",
-
 "quote",
-
 "rustversion",
-
 "syn",
-
]
-

-
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2996,12 +2578,6 @@ dependencies = [
]

[[package]]
-
name = "tap"
-
version = "1.0.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-

-
[[package]]
name = "tar"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3109,15 +2685,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ec32dde57efb15c035ac074118d7f32820451395f28cb0524a01d4e94983b26"

[[package]]
-
name = "tiny-keccak"
-
version = "2.0.2"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
-
dependencies = [
-
 "crunchy",
-
]
-

-
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3176,23 +2743,6 @@ dependencies = [
]

[[package]]
-
name = "toml_datetime"
-
version = "0.5.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
-

-
[[package]]
-
name = "toml_edit"
-
version = "0.18.0"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
-
dependencies = [
-
 "indexmap",
-
 "nom8",
-
 "toml_datetime",
-
]
-

-
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3328,18 +2878,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"

[[package]]
-
name = "uint"
-
version = "0.9.5"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
-
dependencies = [
-
 "byteorder",
-
 "crunchy",
-
 "hex",
-
 "static_assertions",
-
]
-

-
[[package]]
name = "unicode-bidi"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3383,6 +2921,20 @@ dependencies = [
]

[[package]]
+
name = "ureq"
+
version = "2.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
+
dependencies = [
+
 "base64",
+
 "log",
+
 "once_cell",
+
 "serde",
+
 "serde_json",
+
 "url",
+
]
+

+
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3582,15 +3134,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

[[package]]
-
name = "wyz"
-
version = "0.5.1"
-
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
-
dependencies = [
-
 "tap",
-
]
-

-
[[package]]
name = "xattr"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified radicle-cli/Cargo.toml
@@ -24,6 +24,7 @@ serde_json = { version = "1" }
serde_yaml = { version = "0.8" }
thiserror = { version = "1" }
timeago = { version = "0.3", default-features = false }
+
ureq = { version = "2.6.1", default-features = false, features = ["json"] }
zeroize = { version = "1.1" }

[dependencies.radicle]
modified radicle-cli/src/commands.rs
@@ -42,3 +42,5 @@ pub mod rad_track;
pub mod rad_unassign;
#[path = "commands/untrack.rs"]
pub mod rad_untrack;
+
#[path = "commands/web.rs"]
+
pub mod rad_web;
added radicle-cli/src/commands/web.rs
@@ -0,0 +1,102 @@
+
use std::ffi::OsString;
+

+
use anyhow::anyhow;
+
use radicle::crypto::{PublicKey, Signature, Signer};
+
use serde::{Deserialize, Serialize};
+

+
use crate::terminal as term;
+
use crate::terminal::args::{Args, Error, Help};
+

+
pub const HELP: Help = Help {
+
    name: "web",
+
    description: "Connect web with node",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad web [<options>...]
+

+
Options
+

+
    --host, -h             httpd host to bind to
+
    --web, -w              interface host to bind to
+
    --verbose, -v          Verbose output
+
    --help                 Print help
+
"#,
+
};
+

+
#[derive(Debug, Clone, Deserialize, Serialize)]
+
#[serde(rename_all = "camelCase")]
+
pub struct SessionInfo {
+
    pub session_id: String,
+
    pub public_key: PublicKey,
+
}
+

+
#[derive(Debug)]
+
pub struct Options {
+
    pub host: String,
+
    pub web: String,
+
    pub verbose: bool,
+
}
+

+
impl Args for Options {
+
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+
        use lexopt::prelude::*;
+

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut host = None;
+
        let mut web = None;
+
        let mut verbose = false;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("verbose") | Short('v') => verbose = true,
+
                Long("host") | Short('h') => {
+
                    host = Some(parser.value()?.to_string_lossy().to_string())
+
                }
+
                Long("web") | Short('w') => {
+
                    web = Some(parser.value()?.to_string_lossy().to_string())
+
                }
+
                Long("help") => {
+
                    return Err(Error::Help.into());
+
                }
+
                _ => {
+
                    return Err(anyhow!(arg.unexpected()));
+
                }
+
            }
+
        }
+

+
        Ok((
+
            Options {
+
                verbose,
+
                host: host.unwrap_or(String::from("0.0.0.0:8080")),
+
                web: web.unwrap_or(String::from("0.0.0.0:3000")),
+
            },
+
            vec![],
+
        ))
+
    }
+
}
+

+
pub fn sign(signer: Box<dyn Signer>, session: &SessionInfo) -> Result<Signature, anyhow::Error> {
+
    signer
+
        .try_sign(format!("{}:{}", session.session_id, session.public_key).as_bytes())
+
        .map_err(anyhow::Error::from)
+
}
+

+
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
    let session: SessionInfo = ureq::post(&format!("http://{}/api/v1/sessions", options.host))
+
        .call()?
+
        .into_json()?;
+
    let profile = ctx.profile()?;
+
    let signer = profile.signer()?;
+
    let signature = sign(signer, &session)?;
+
    term::info!(
+
        "http://{}/session/{}?pk={}&sig={}",
+
        options.web,
+
        session.session_id,
+
        session.public_key,
+
        signature,
+
    );
+

+
    Ok(())
+
}
modified radicle-cli/src/main.rs
@@ -278,6 +278,12 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
+
        "web" => term::run_command_args::<rad_web::Options, _>(
+
            rad_web::HELP,
+
            "Web",
+
            rad_web::run,
+
            args.to_vec(),
+
        ),
        _ => {
            let exe = format!("{}-{}", NAME, exe);
            let status = process::Command::new(exe.clone()).args(args).status();
modified radicle-httpd/Cargo.toml
@@ -19,14 +19,12 @@ anyhow = { version = "1" }
axum = { version = "0.6.2", default-features = false, features = ["headers", "json", "query", "tokio"] }
axum-server = { version = "0.4.4", default-features = false }
chrono = { version = "0.4.22" }
-
ethers-core = { version = "1.0" }
fastrand = { version = "1.7.0" }
flate2 = { version = "1" }
hyper = { version = "0.14.17", default-features = false }
lexopt = { version = "0.2.1" }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
-
siwe = { version = "0.5" }
thiserror = { version = "1" }
time = { version = "0.3.17" }
tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] }
modified radicle-httpd/src/api.rs
@@ -73,7 +73,7 @@ pub fn router(ctx: Context) -> Router {
            CorsLayer::new()
                .max_age(Duration::from_secs(86400))
                .allow_origin(cors::Any)
-
                .allow_methods([Method::GET, Method::POST, Method::PUT])
+
                .allow_methods([Method::GET, Method::POST, Method::PATCH, Method::PUT])
                .allow_headers([CONTENT_TYPE, AUTHORIZATION]),
        )
}
modified radicle-httpd/src/api/auth.rs
@@ -1,13 +1,8 @@
-
use std::convert::TryFrom;
-
use std::str::FromStr;
-

-
use ethers_core::types::{Signature, H160};
-
use serde::{Deserialize, Serialize, Serializer};
+
use radicle::crypto::PublicKey;
+
use serde::{Serialize, Serializer};
use time::OffsetDateTime;

-
use crate::api::error::Error;
-

-
#[derive(Clone)]
+
#[derive(Clone, PartialEq, PartialOrd)]
pub struct DateTime(pub OffsetDateTime);

impl Serialize for DateTime {
@@ -16,26 +11,11 @@ impl Serialize for DateTime {
    }
}

-
#[derive(Deserialize, Serialize)]
-
pub struct AuthRequest {
-
    pub message: String,
-
    #[serde(deserialize_with = "deserialize_signature")]
-
    pub signature: Signature,
-
}
-

-
fn deserialize_signature<'de, D>(deserializer: D) -> Result<Signature, D::Error>
-
where
-
    D: serde::de::Deserializer<'de>,
-
{
-
    let buf = String::deserialize(deserializer)?;
-
    Signature::from_str(&buf).map_err(serde::de::Error::custom)
-
}
-

pub enum AuthState {
    Authorized(Session),
    Unauthorized {
-
        nonce: String,
-
        expiration_time: DateTime,
+
        public_key: PublicKey,
+
        expires_at: DateTime,
    },
}

@@ -43,48 +23,7 @@ pub enum AuthState {
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Session {
-
    pub domain: String,
-
    pub address: H160,
-
    pub statement: Option<String>,
-
    pub uri: String,
-
    pub version: u64,
-
    pub chain_id: u64,
-
    pub nonce: String,
+
    pub public_key: PublicKey,
    pub issued_at: DateTime,
-
    pub expiration_time: Option<DateTime>,
-
    pub resources: Vec<String>,
-
}
-

-
impl TryFrom<siwe::Message> for Session {
-
    type Error = Error;
-

-
    fn try_from(message: siwe::Message) -> Result<Session, Error> {
-
        Ok(Session {
-
            domain: message.domain.host().to_string(),
-
            address: H160(message.address),
-
            statement: None,
-
            uri: message.uri.to_string(),
-
            version: message.version as u64,
-
            chain_id: message.chain_id,
-
            nonce: message.nonce,
-
            issued_at: DateTime(message.issued_at.as_ref().to_owned()),
-
            expiration_time: message
-
                .expiration_time
-
                .map(|x| DateTime(x.as_ref().to_owned())),
-
            resources: message.resources.iter().map(|r| r.to_string()).collect(),
-
        })
-
    }
-
}
-

-
#[cfg(test)]
-
mod test {
-
    #[test]
-
    fn test_auth_request_de() {
-
        let json = serde_json::json!({
-
            "message": "Hello World!",
-
            "signature": "20096c6ed2bcccb88c9cafbbbbda7a5a3cff6d0ca318c07faa58464083ca40a92f899fbeb26a4c763a7004b13fd0f1ba6c321d4e3a023e30f63c40d4154b99a41c"
-
        });
-

-
        let _req: super::AuthRequest = serde_json::from_value(json).unwrap();
-
    }
+
    pub expires_at: DateTime,
}
modified radicle-httpd/src/api/error.rs
@@ -18,13 +18,13 @@ pub enum Error {
    #[error(transparent)]
    Env(#[from] std::env::VarError),

-
    /// An error occurred while verifying the siwe message.
+
    /// Profile error.
    #[error(transparent)]
-
    SiweVerification(#[from] siwe::VerificationError),
+
    Profile(#[from] radicle::profile::Error),

-
    /// An error occurred while parsing the siwe message.
+
    /// Crypto error.
    #[error(transparent)]
-
    SiweParse(#[from] siwe::ParseError),
+
    Crypto(#[from] radicle::crypto::Error),

    /// Storage error.
    #[error(transparent)]
@@ -60,8 +60,7 @@ impl IntoResponse for Error {
        let (status, msg) = match &self {
            Error::NotFound => (StatusCode::NOT_FOUND, None),
            Error::Auth(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
-
            Error::SiweParse(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
-
            Error::SiweVerification(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
+
            Error::Crypto(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
            Error::Git2(e) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                Some(e.message().to_owned()),
modified radicle-httpd/src/api/test.rs
@@ -3,7 +3,7 @@ use std::sync::Arc;
use std::{env, fs};

use axum::body::Body;
-
use axum::http::Request;
+
use axum::http::{Method, Request};
use axum::Router;
use serde_json::Value;
use tower::ServiceExt;
@@ -116,20 +116,42 @@ pub fn seed(dir: &Path) -> Context {
    }
}

-
pub async fn request(app: &Router, path: impl ToString) -> Response {
+
pub async fn get(app: &Router, path: impl ToString) -> Response {
    Response(
        app.clone()
-
            .oneshot(
-
                Request::builder()
-
                    .uri(path.to_string())
-
                    .body(Body::empty())
-
                    .unwrap(),
-
            )
+
            .oneshot(request(path, Method::GET, None))
            .await
            .unwrap(),
    )
}

+
pub async fn post(app: &Router, path: impl ToString, body: Option<Body>) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::POST, body))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
pub async fn put(app: &Router, path: impl ToString, body: Option<Body>) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::PUT, body))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
fn request(path: impl ToString, method: Method, body: Option<Body>) -> Request<Body> {
+
    Request::builder()
+
        .method(method)
+
        .uri(path.to_string())
+
        .header("Content-Type", "application/json")
+
        .body(body.unwrap_or_else(Body::empty))
+
        .unwrap()
+
}
+

pub struct Response(axum::response::Response);

impl Response {
modified radicle-httpd/src/api/v1/delegates.rs
@@ -69,13 +69,13 @@ mod routes {
    use axum::http::StatusCode;
    use serde_json::json;

-
    use crate::api::test::{self, request, HEAD};
+
    use crate::api::test::{self, get, HEAD};

    #[tokio::test]
    async fn test_delegates_projects() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(
+
        let response = get(
            &app,
            "/delegates/did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/projects",
        )
modified radicle-httpd/src/api/v1/projects.rs
@@ -465,13 +465,13 @@ mod routes {
    use axum::http::StatusCode;
    use serde_json::json;

-
    use crate::api::test::{self, request, HEAD, HEAD_1};
+
    use crate::api::test::{self, get, HEAD, HEAD_1};

    #[tokio::test]
    async fn test_projects_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects").await;
+
        let response = get(&app, "/projects").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -494,7 +494,7 @@ mod routes {
    async fn test_projects() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp").await;
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -515,7 +515,7 @@ mod routes {
    async fn test_projects_commits_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/commits").await;
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/commits").await;

        assert_eq!(response.status(), StatusCode::FOUND);
        assert_eq!(
@@ -637,7 +637,7 @@ mod routes {
    async fn test_projects_commits() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(
+
        let response = get(
            &app,
            format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/commits/{HEAD}"),
        )
@@ -703,7 +703,7 @@ mod routes {
    async fn test_projects_tree() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(
+
        let response = get(
            &app,
            format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/"),
        )
@@ -750,7 +750,7 @@ mod routes {
            )
        );

-
        let response = request(
+
        let response = get(
            &app,
            format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/dir1"),
        )
@@ -796,7 +796,7 @@ mod routes {
    async fn test_projects_remotes_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/remotes").await;
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/remotes").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -817,7 +817,7 @@ mod routes {
    async fn test_projects_remotes() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi").await;
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
@@ -836,7 +836,7 @@ mod routes {
    async fn test_projects_blob() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(
+
        let response = get(
            &app,
            format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/blob/{HEAD}/README"),
        )
@@ -872,7 +872,7 @@ mod routes {
    async fn test_projects_readme() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(
+
        let response = get(
            &app,
            format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/readme/{HEAD}"),
        )
@@ -908,7 +908,7 @@ mod routes {
    async fn test_projects_issues_root() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/issues").await;
+
        let response = get(&app, "/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/issues").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
modified radicle-httpd/src/api/v1/sessions.rs
@@ -1,131 +1,135 @@
-
use std::collections::HashMap;
-
use std::convert::TryInto;
-
use std::env;
use std::iter::repeat_with;
-
use std::str::FromStr;

use axum::extract::State;
use axum::response::IntoResponse;
-
use axum::routing::{get, post};
+
use axum::routing::{post, put};
use axum::{Json, Router};
-
use ethers_core::utils::hex;
-
use hyper::http::uri::Authority;
+
use radicle::crypto::{PublicKey, Signature};
+
use serde::{Deserialize, Serialize};
use serde_json::json;
-
use siwe::Message;
use time::{Duration, OffsetDateTime};

-
use crate::api::auth::{AuthRequest, AuthState, DateTime, Session};
+
use crate::api::auth::{AuthState, DateTime, Session};
use crate::api::axum_extra::Path;
use crate::api::error::Error;
use crate::api::Context;

pub const UNAUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::seconds(60);
+
pub const AUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::weeks(1);

pub fn router(ctx: Context) -> Router {
    Router::new()
        .route("/sessions", post(session_create_handler))
-
        .route(
-
            "/sessions/:id",
-
            get(session_get_handler).put(session_signin_handler),
-
        )
+
        .route("/sessions/:id", put(session_signin_handler))
        .with_state(ctx)
}

+
#[derive(Debug, Deserialize, Serialize)]
+
struct AuthChallenge {
+
    sig: Signature,
+
    pk: PublicKey,
+
}
+

/// Create session.
/// `POST /sessions`
async fn session_create_handler(State(ctx): State<Context>) -> impl IntoResponse {
+
    let rng = fastrand::Rng::new();
+
    let session_id = repeat_with(|| rng.alphanumeric())
+
        .take(32)
+
        .collect::<String>();
+
    let signer = ctx.profile.signer().map_err(Error::from)?;
    let expiration_time = OffsetDateTime::now_utc()
        .checked_add(UNAUTHORIZED_SESSIONS_EXPIRATION)
        .unwrap();
+
    let auth_state = AuthState::Unauthorized {
+
        public_key: *signer.public_key(),
+
        expires_at: DateTime(expiration_time),
+
    };
    let mut sessions = ctx.sessions.write().await;
-
    let (session_id, nonce) = create_session(&mut sessions, DateTime(expiration_time));
-

-
    Json(json!({ "id": session_id, "nonce": nonce }))
-
}
-

-
/// Get session.
-
/// `GET /sessions/:id`
-
async fn session_get_handler(
-
    State(ctx): State<Context>,
-
    Path(id): Path<String>,
-
) -> impl IntoResponse {
-
    let sessions = ctx.sessions.read().await;
-
    let session = sessions.get(&id).ok_or(Error::NotFound)?;
+
    sessions.insert(session_id.clone(), auth_state);

-
    match session {
-
        AuthState::Authorized(session) => {
-
            Ok::<_, Error>(Json(json!({ "id": id, "session": session })))
-
        }
-
        AuthState::Unauthorized {
-
            nonce,
-
            expiration_time,
-
        } => Ok::<_, Error>(Json(
-
            json!({ "id": id, "nonce": nonce, "expirationTime": expiration_time }),
-
        )),
-
    }
+
    Ok::<_, Error>(Json(
+
        json!({"sessionId": session_id, "publicKey": signer.public_key()}),
+
    ))
}

/// Update session.
/// `PUT /sessions/:id`
async fn session_signin_handler(
    State(ctx): State<Context>,
-
    Path(id): Path<String>,
-
    Json(request): Json<AuthRequest>,
+
    Path(session_id): Path<String>,
+
    Json(request): Json<AuthChallenge>,
) -> impl IntoResponse {
-
    // Get unauthenticated session data, return early if not found
    let mut sessions = ctx.sessions.write().await;
-
    let session = sessions.get(&id).ok_or(Error::NotFound)?;
-

-
    if let AuthState::Unauthorized { nonce, .. } = session {
-
        let message = Message::from_str(request.message.as_str()).map_err(Error::from)?;
-

-
        let host = env::var("RADICLE_DOMAIN").map_err(Error::from)?;
-

-
        // Validate nonce
-
        if *nonce != message.nonce {
-
            return Err(Error::Auth("Invalid nonce"));
+
    let session = sessions.get(&session_id).ok_or(Error::NotFound)?;
+
    if let AuthState::Unauthorized {
+
        public_key,
+
        expires_at,
+
    } = session
+
    {
+
        if public_key != &request.pk {
+
            return Err(Error::Auth("Invalid public key"));
        }
-

-
        // Verify that domain is the correct one
-
        let authority = Authority::from_str(&host).map_err(|_| Error::Auth("Invalid host"))?;
-
        if authority != message.domain {
-
            return Err(Error::Auth("Invalid domain"));
+
        if expires_at <= &DateTime(OffsetDateTime::now_utc()) {
+
            return Err(Error::Auth("Session expired"));
        }
-

-
        // Verifies the following:
-
        // - AuthRequest sig matches the address passed in the AuthRequest message.
-
        // - expirationTime is not in the past.
-
        // - notBefore time is in the future.
-
        message
-
            .verify(&request.signature.to_vec(), &Default::default())
-
            .await
+
        let payload = format!("{}:{}", session_id, request.pk);
+
        request
+
            .pk
+
            .verify(payload.as_bytes(), &request.sig)
            .map_err(Error::from)?;
-

-
        let session: Session = message.try_into()?;
-
        sessions.insert(id.clone(), AuthState::Authorized(session.clone()));
-

-
        return Ok::<_, Error>(Json(json!({ "id": id, "session": session })));
+
        let expiration_time = OffsetDateTime::now_utc()
+
            .checked_add(AUTHORIZED_SESSIONS_EXPIRATION)
+
            .unwrap();
+
        let session = Session {
+
            public_key: request.pk,
+
            issued_at: DateTime(OffsetDateTime::now_utc()),
+
            expires_at: DateTime(expiration_time),
+
        };
+
        sessions.insert(session_id.clone(), AuthState::Authorized(session));
+

+
        return Ok::<_, Error>(());
    }

    Err(Error::Auth("Session already authorized"))
}

-
fn create_session(
-
    map: &mut HashMap<String, AuthState>,
-
    expiration_time: DateTime,
-
) -> (String, String) {
-
    let nonce = siwe::generate_nonce();
-

-
    // We generate a value from the RNG for the session id
-
    let rng = fastrand::Rng::new();
-
    let id = hex::encode(repeat_with(|| rng.u8(..)).take(32).collect::<Vec<u8>>());
-

-
    let auth_state = AuthState::Unauthorized {
-
        nonce: nonce.clone(),
-
        expiration_time,
-
    };
+
#[cfg(test)]
+
mod routes {
+
    use axum::body::Body;
+
    use axum::http::StatusCode;
+
    use radicle_cli::commands::rad_web::{self, SessionInfo};
+

+
    use crate::api::test::{self, post, put};
+

+
    #[tokio::test]
+
    async fn test_session() {
+
        let tmp = tempfile::tempdir().unwrap();
+
        let ctx = test::seed(tmp.path());
+
        let app = super::router(ctx.to_owned());
+

+
        // Create session
+
        let response = post(&app, "/sessions", None).await;
+
        assert_eq!(response.status(), StatusCode::OK);
+
        let json = response.json().await;
+
        let session_info: SessionInfo = serde_json::from_value(json).unwrap();
+

+
        // Create request body
+
        let signer = ctx.profile.signer().unwrap();
+
        let signature = rad_web::sign(signer, &session_info).unwrap();
+
        let body = serde_json::to_vec(&super::AuthChallenge {
+
            sig: signature,
+
            pk: session_info.public_key,
+
        })
+
        .unwrap();

-
    map.insert(id.clone(), auth_state);
+
        let response = put(
+
            &app,
+
            format!("/sessions/{}", session_info.session_id),
+
            Some(Body::from(body)),
+
        )
+
        .await;

-
    (id, nonce)
+
        assert_eq!(response.status(), StatusCode::OK);
+
    }
}
modified radicle-httpd/src/api/v1/stats.rs
@@ -29,13 +29,13 @@ mod routes {
    use axum::http::StatusCode;
    use serde_json::json;

-
    use crate::api::test::{self, request};
+
    use crate::api::test::{self, get};

    #[tokio::test]
    async fn test_stats() {
        let tmp = tempfile::tempdir().unwrap();
        let app = super::router(test::seed(tmp.path()));
-
        let response = request(&app, "/stats").await;
+
        let response = get(&app, "/stats").await;

        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(