Add web-based authentication

Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
Sebastian Martinez 2023-01-27 09:46:03 +01:00 committed by Alexis Sellier
parent deeb160c2d
commit f07a875ce1
No known key found for this signature in database
14 changed files with 270 additions and 654 deletions

487
Cargo.lock generated
View File

@ -29,15 +29,6 @@ dependencies = [
"cpufeatures", "cpufeatures",
] ]
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "amplify" name = "amplify"
version = "4.0.0-beta.7" version = "4.0.0-beta.7"
@ -94,12 +85,6 @@ version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]] [[package]]
name = "ascii" name = "ascii"
version = "1.1.0" version = "1.1.0"
@ -128,18 +113,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -259,18 +232,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.9.0" version = "0.9.0"
@ -314,12 +275,6 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byte-slice-cast"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -331,9 +286,6 @@ name = "bytes"
version = "1.3.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "cc" name = "cc"
@ -500,12 +452,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "crypto-bigint" name = "crypto-bigint"
version = "0.4.9" version = "0.4.9"
@ -685,17 +631,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[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]] [[package]]
name = "dialoguer" name = "dialoguer"
version = "0.10.3" version = "0.10.3"
@ -837,80 +772,6 @@ dependencies = [
"libc", "libc",
] ]
[[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]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.8.0" version = "1.8.0"
@ -942,18 +803,6 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[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]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.25" version = "1.0.25"
@ -979,12 +828,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.25" version = "0.3.25"
@ -1180,12 +1023,6 @@ dependencies = [
"http", "http",
] ]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -1204,12 +1041,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@ -1317,44 +1148,6 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[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]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.2" version = "1.9.2"
@ -1417,16 +1210,6 @@ dependencies = [
"popol", "popol",
] ]
[[package]]
name = "iri-string"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0586ad318a04c73acdbad33f67969519b5452c80770c4c72059a686da48a7e"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.2" version = "0.4.2"
@ -1474,19 +1257,6 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[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]] [[package]]
name = "keccak" name = "keccak"
version = "0.1.3" version = "0.1.3"
@ -1684,15 +1454,6 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "nonempty" name = "nonempty"
version = "0.5.0" version = "0.5.0"
@ -1811,31 +1572,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 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]] [[package]]
name = "output_vt100" name = "output_vt100"
version = "0.1.3" version = "0.1.3"
@ -1873,32 +1609,6 @@ dependencies = [
"sha2 0.10.6", "sha2 0.10.6",
] ]
[[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]] [[package]]
name = "pbkdf2" name = "pbkdf2"
version = "0.11.0" version = "0.11.0"
@ -2021,30 +1731,6 @@ dependencies = [
"yansi", "yansi",
] ]
[[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]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -2162,6 +1848,7 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"timeago", "timeago",
"ureq",
"zeroize", "zeroize",
] ]
@ -2252,7 +1939,6 @@ dependencies = [
"axum", "axum",
"axum-server", "axum-server",
"chrono", "chrono",
"ethers-core",
"fastrand", "fastrand",
"flate2", "flate2",
"hyper", "hyper",
@ -2263,7 +1949,6 @@ dependencies = [
"radicle-surf", "radicle-surf",
"serde", "serde",
"serde_json", "serde_json",
"siwe",
"tempfile", "tempfile",
"thiserror", "thiserror",
"time 0.3.17", "time 0.3.17",
@ -2360,12 +2045,6 @@ dependencies = [
"radicle", "radicle",
] ]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -2452,8 +2131,6 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [ dependencies = [
"aho-corasick",
"memchr",
"regex-syntax", "regex-syntax",
] ]
@ -2492,27 +2169,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[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]] [[package]]
name = "rsa" name = "rsa"
version = "0.7.2" version = "0.7.2"
@ -2534,12 +2190,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustc-hex"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.36.7" version = "0.36.7"
@ -2575,30 +2225,6 @@ dependencies = [
"cipher", "cipher",
] ]
[[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]] [[package]]
name = "scratch" name = "scratch"
version = "1.0.3" version = "1.0.3"
@ -2784,22 +2410,6 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 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]] [[package]]
name = "slab" name = "slab"
version = "0.4.7" version = "0.4.7"
@ -2932,34 +2542,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[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]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.1" version = "2.4.1"
@ -2995,12 +2577,6 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.38" version = "0.4.38"
@ -3108,15 +2684,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ec32dde57efb15c035ac074118d7f32820451395f28cb0524a01d4e94983b26" 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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -3175,23 +2742,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[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]] [[package]]
name = "tower" name = "tower"
version = "0.4.13" version = "0.4.13"
@ -3327,18 +2877,6 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 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]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.10" version = "0.3.10"
@ -3382,6 +2920,20 @@ dependencies = [
"subtle", "subtle",
] ]
[[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]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.3.1"
@ -3581,15 +3133,6 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]] [[package]]
name = "xattr" name = "xattr"
version = "0.2.3" version = "0.2.3"

View File

@ -24,6 +24,7 @@ serde_json = { version = "1" }
serde_yaml = { version = "0.8" } serde_yaml = { version = "0.8" }
thiserror = { version = "1" } thiserror = { version = "1" }
timeago = { version = "0.3", default-features = false } timeago = { version = "0.3", default-features = false }
ureq = { version = "2.6.1", default-features = false, features = ["json"] }
zeroize = { version = "1.1" } zeroize = { version = "1.1" }
[dependencies.radicle] [dependencies.radicle]

View File

@ -42,3 +42,5 @@ pub mod rad_track;
pub mod rad_unassign; pub mod rad_unassign;
#[path = "commands/untrack.rs"] #[path = "commands/untrack.rs"]
pub mod rad_untrack; pub mod rad_untrack;
#[path = "commands/web.rs"]
pub mod rad_web;

View File

@ -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(())
}

View File

@ -278,6 +278,12 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
args.to_vec(), 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 exe = format!("{}-{}", NAME, exe);
let status = process::Command::new(exe.clone()).args(args).status(); let status = process::Command::new(exe.clone()).args(args).status();

View File

@ -19,14 +19,12 @@ anyhow = { version = "1" }
axum = { version = "0.6.2", default-features = false, features = ["headers", "json", "query", "tokio"] } axum = { version = "0.6.2", default-features = false, features = ["headers", "json", "query", "tokio"] }
axum-server = { version = "0.4.4", default-features = false } axum-server = { version = "0.4.4", default-features = false }
chrono = { version = "0.4.22" } chrono = { version = "0.4.22" }
ethers-core = { version = "1.0" }
fastrand = { version = "1.7.0" } fastrand = { version = "1.7.0" }
flate2 = { version = "1" } flate2 = { version = "1" }
hyper = { version = "0.14.17", default-features = false } hyper = { version = "0.14.17", default-features = false }
lexopt = { version = "0.2.1" } lexopt = { version = "0.2.1" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] } serde_json = { version = "1", features = ["preserve_order"] }
siwe = { version = "0.5" }
thiserror = { version = "1" } thiserror = { version = "1" }
time = { version = "0.3.17" } time = { version = "0.3.17" }
tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] } tokio = { version = "1.21", default-features = false, features = ["macros", "rt-multi-thread"] }

View File

@ -73,7 +73,7 @@ pub fn router(ctx: Context) -> Router {
CorsLayer::new() CorsLayer::new()
.max_age(Duration::from_secs(86400)) .max_age(Duration::from_secs(86400))
.allow_origin(cors::Any) .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]), .allow_headers([CONTENT_TYPE, AUTHORIZATION]),
) )
} }

View File

@ -1,13 +1,8 @@
use std::convert::TryFrom; use radicle::crypto::PublicKey;
use std::str::FromStr; use serde::{Serialize, Serializer};
use ethers_core::types::{Signature, H160};
use serde::{Deserialize, Serialize, Serializer};
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::api::error::Error; #[derive(Clone, PartialEq, PartialOrd)]
#[derive(Clone)]
pub struct DateTime(pub OffsetDateTime); pub struct DateTime(pub OffsetDateTime);
impl Serialize for DateTime { 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 { pub enum AuthState {
Authorized(Session), Authorized(Session),
Unauthorized { Unauthorized {
nonce: String, public_key: PublicKey,
expiration_time: DateTime, expires_at: DateTime,
}, },
} }
@ -43,48 +23,7 @@ pub enum AuthState {
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Session { pub struct Session {
pub domain: String, pub public_key: PublicKey,
pub address: H160,
pub statement: Option<String>,
pub uri: String,
pub version: u64,
pub chain_id: u64,
pub nonce: String,
pub issued_at: DateTime, pub issued_at: DateTime,
pub expiration_time: Option<DateTime>, pub expires_at: 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();
}
} }

View File

@ -18,13 +18,13 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
Env(#[from] std::env::VarError), Env(#[from] std::env::VarError),
/// An error occurred while verifying the siwe message. /// Profile error.
#[error(transparent)] #[error(transparent)]
SiweVerification(#[from] siwe::VerificationError), Profile(#[from] radicle::profile::Error),
/// An error occurred while parsing the siwe message. /// Crypto error.
#[error(transparent)] #[error(transparent)]
SiweParse(#[from] siwe::ParseError), Crypto(#[from] radicle::crypto::Error),
/// Storage error. /// Storage error.
#[error(transparent)] #[error(transparent)]
@ -60,8 +60,7 @@ impl IntoResponse for Error {
let (status, msg) = match &self { let (status, msg) = match &self {
Error::NotFound => (StatusCode::NOT_FOUND, None), Error::NotFound => (StatusCode::NOT_FOUND, None),
Error::Auth(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())), Error::Auth(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
Error::SiweParse(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())), Error::Crypto(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
Error::SiweVerification(msg) => (StatusCode::BAD_REQUEST, Some(msg.to_string())),
Error::Git2(e) => ( Error::Git2(e) => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
Some(e.message().to_owned()), Some(e.message().to_owned()),

View File

@ -3,7 +3,7 @@ use std::sync::Arc;
use std::{env, fs}; use std::{env, fs};
use axum::body::Body; use axum::body::Body;
use axum::http::Request; use axum::http::{Method, Request};
use axum::Router; use axum::Router;
use serde_json::Value; use serde_json::Value;
use tower::ServiceExt; 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( Response(
app.clone() app.clone()
.oneshot( .oneshot(request(path, Method::GET, None))
Request::builder()
.uri(path.to_string())
.body(Body::empty())
.unwrap(),
)
.await .await
.unwrap(), .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); pub struct Response(axum::response::Response);
impl Response { impl Response {

View File

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

View File

@ -465,13 +465,13 @@ mod routes {
use axum::http::StatusCode; use axum::http::StatusCode;
use serde_json::json; use serde_json::json;
use crate::api::test::{self, request, HEAD, HEAD_1}; use crate::api::test::{self, get, HEAD, HEAD_1};
#[tokio::test] #[tokio::test]
async fn test_projects_root() { async fn test_projects_root() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -494,7 +494,7 @@ mod routes {
async fn test_projects() { async fn test_projects() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -515,7 +515,7 @@ mod routes {
async fn test_projects_commits_root() { async fn test_projects_commits_root() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::FOUND);
assert_eq!( assert_eq!(
@ -637,7 +637,7 @@ mod routes {
async fn test_projects_commits() { async fn test_projects_commits() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); let app = super::router(test::seed(tmp.path()));
let response = request( let response = get(
&app, &app,
format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/commits/{HEAD}"), format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/commits/{HEAD}"),
) )
@ -703,7 +703,7 @@ mod routes {
async fn test_projects_tree() { async fn test_projects_tree() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); let app = super::router(test::seed(tmp.path()));
let response = request( let response = get(
&app, &app,
format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/"), format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/"),
) )
@ -750,7 +750,7 @@ mod routes {
) )
); );
let response = request( let response = get(
&app, &app,
format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/dir1"), format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/tree/{HEAD}/dir1"),
) )
@ -796,7 +796,7 @@ mod routes {
async fn test_projects_remotes_root() { async fn test_projects_remotes_root() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -817,7 +817,7 @@ mod routes {
async fn test_projects_remotes() { async fn test_projects_remotes() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -836,7 +836,7 @@ mod routes {
async fn test_projects_blob() { async fn test_projects_blob() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); let app = super::router(test::seed(tmp.path()));
let response = request( let response = get(
&app, &app,
format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/blob/{HEAD}/README"), format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/blob/{HEAD}/README"),
) )
@ -872,7 +872,7 @@ mod routes {
async fn test_projects_readme() { async fn test_projects_readme() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); let app = super::router(test::seed(tmp.path()));
let response = request( let response = get(
&app, &app,
format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/readme/{HEAD}"), format!("/projects/rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp/readme/{HEAD}"),
) )
@ -908,7 +908,7 @@ mod routes {
async fn test_projects_issues_root() { async fn test_projects_issues_root() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(

View File

@ -1,131 +1,135 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::env;
use std::iter::repeat_with; use std::iter::repeat_with;
use std::str::FromStr;
use axum::extract::State; use axum::extract::State;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::routing::{get, post}; use axum::routing::{post, put};
use axum::{Json, Router}; use axum::{Json, Router};
use ethers_core::utils::hex; use radicle::crypto::{PublicKey, Signature};
use hyper::http::uri::Authority; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use siwe::Message;
use time::{Duration, OffsetDateTime}; 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::axum_extra::Path;
use crate::api::error::Error; use crate::api::error::Error;
use crate::api::Context; use crate::api::Context;
pub const UNAUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::seconds(60); pub const UNAUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::seconds(60);
pub const AUTHORIZED_SESSIONS_EXPIRATION: Duration = Duration::weeks(1);
pub fn router(ctx: Context) -> Router { pub fn router(ctx: Context) -> Router {
Router::new() Router::new()
.route("/sessions", post(session_create_handler)) .route("/sessions", post(session_create_handler))
.route( .route("/sessions/:id", put(session_signin_handler))
"/sessions/:id",
get(session_get_handler).put(session_signin_handler),
)
.with_state(ctx) .with_state(ctx)
} }
#[derive(Debug, Deserialize, Serialize)]
struct AuthChallenge {
sig: Signature,
pk: PublicKey,
}
/// Create session. /// Create session.
/// `POST /sessions` /// `POST /sessions`
async fn session_create_handler(State(ctx): State<Context>) -> impl IntoResponse { 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() let expiration_time = OffsetDateTime::now_utc()
.checked_add(UNAUTHORIZED_SESSIONS_EXPIRATION) .checked_add(UNAUTHORIZED_SESSIONS_EXPIRATION)
.unwrap(); .unwrap();
let auth_state = AuthState::Unauthorized {
public_key: *signer.public_key(),
expires_at: DateTime(expiration_time),
};
let mut sessions = ctx.sessions.write().await; let mut sessions = ctx.sessions.write().await;
let (session_id, nonce) = create_session(&mut sessions, DateTime(expiration_time)); sessions.insert(session_id.clone(), auth_state);
Json(json!({ "id": session_id, "nonce": nonce })) Ok::<_, Error>(Json(
} json!({"sessionId": session_id, "publicKey": signer.public_key()}),
))
/// 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)?;
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 }),
)),
}
} }
/// Update session. /// Update session.
/// `PUT /sessions/:id` /// `PUT /sessions/:id`
async fn session_signin_handler( async fn session_signin_handler(
State(ctx): State<Context>, State(ctx): State<Context>,
Path(id): Path<String>, Path(session_id): Path<String>,
Json(request): Json<AuthRequest>, Json(request): Json<AuthChallenge>,
) -> impl IntoResponse { ) -> impl IntoResponse {
// Get unauthenticated session data, return early if not found
let mut sessions = ctx.sessions.write().await; let mut sessions = ctx.sessions.write().await;
let session = sessions.get(&id).ok_or(Error::NotFound)?; let session = sessions.get(&session_id).ok_or(Error::NotFound)?;
if let AuthState::Unauthorized {
if let AuthState::Unauthorized { nonce, .. } = session { public_key,
let message = Message::from_str(request.message.as_str()).map_err(Error::from)?; expires_at,
} = session
let host = env::var("RADICLE_DOMAIN").map_err(Error::from)?; {
if public_key != &request.pk {
// Validate nonce return Err(Error::Auth("Invalid public key"));
if *nonce != message.nonce {
return Err(Error::Auth("Invalid nonce"));
} }
if expires_at <= &DateTime(OffsetDateTime::now_utc()) {
// Verify that domain is the correct one return Err(Error::Auth("Session expired"));
let authority = Authority::from_str(&host).map_err(|_| Error::Auth("Invalid host"))?;
if authority != message.domain {
return Err(Error::Auth("Invalid domain"));
} }
let payload = format!("{}:{}", session_id, request.pk);
// Verifies the following: request
// - AuthRequest sig matches the address passed in the AuthRequest message. .pk
// - expirationTime is not in the past. .verify(payload.as_bytes(), &request.sig)
// - notBefore time is in the future.
message
.verify(&request.signature.to_vec(), &Default::default())
.await
.map_err(Error::from)?; .map_err(Error::from)?;
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));
let session: Session = message.try_into()?; return Ok::<_, Error>(());
sessions.insert(id.clone(), AuthState::Authorized(session.clone()));
return Ok::<_, Error>(Json(json!({ "id": id, "session": session })));
} }
Err(Error::Auth("Session already authorized")) Err(Error::Auth("Session already authorized"))
} }
fn create_session( #[cfg(test)]
map: &mut HashMap<String, AuthState>, mod routes {
expiration_time: DateTime, use axum::body::Body;
) -> (String, String) { use axum::http::StatusCode;
let nonce = siwe::generate_nonce(); use radicle_cli::commands::rad_web::{self, SessionInfo};
// We generate a value from the RNG for the session id use crate::api::test::{self, post, put};
let rng = fastrand::Rng::new();
let id = hex::encode(repeat_with(|| rng.u8(..)).take(32).collect::<Vec<u8>>());
let auth_state = AuthState::Unauthorized { #[tokio::test]
nonce: nonce.clone(), async fn test_session() {
expiration_time, let tmp = tempfile::tempdir().unwrap();
}; let ctx = test::seed(tmp.path());
let app = super::router(ctx.to_owned());
map.insert(id.clone(), auth_state); // 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();
(id, nonce) // 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();
let response = put(
&app,
format!("/sessions/{}", session_info.session_id),
Some(Body::from(body)),
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
} }

View File

@ -29,13 +29,13 @@ mod routes {
use axum::http::StatusCode; use axum::http::StatusCode;
use serde_json::json; use serde_json::json;
use crate::api::test::{self, request}; use crate::api::test::{self, get};
#[tokio::test] #[tokio::test]
async fn test_stats() { async fn test_stats() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let app = super::router(test::seed(tmp.path())); 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!(response.status(), StatusCode::OK);
assert_eq!( assert_eq!(