Merge pull request 'cli' (#9) from cli into master
Reviewed-on: https://git2.zettoit.eu/zettoit/bin/pulls/9
This commit is contained in:
commit
a1a2aa2754
14 changed files with 694 additions and 48 deletions
268
Cargo.lock
generated
268
Cargo.lock
generated
|
@ -76,6 +76,54 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -263,7 +311,25 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "binctl"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"clap",
|
||||||
|
"confy",
|
||||||
|
"dirs",
|
||||||
|
"exitcode",
|
||||||
|
"open",
|
||||||
|
"openidconnect",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -356,6 +422,64 @@ dependencies = [
|
||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.38",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "confy"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e37668cb35145dcfaa1931a5f37fde375eeae8068b4c0d2f289da28a270b2d2c"
|
||||||
|
dependencies = [
|
||||||
|
"directories",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"toml 0.5.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
@ -530,6 +654,47 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "directories"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.3.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "5.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys 0.4.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"option-ext",
|
||||||
|
"redox_users",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
|
@ -658,6 +823,12 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exitcode"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -742,6 +913,7 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -1045,6 +1217,15 @@ version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-docker"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1056,6 +1237,16 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-wsl"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
|
||||||
|
dependencies = [
|
||||||
|
"is-docker",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1339,6 +1530,17 @@ 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"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfabf1927dce4d6fdf563d63328a0a506101ced3ec780ca2135747336c98cef8"
|
||||||
|
dependencies = [
|
||||||
|
"is-wsl",
|
||||||
|
"libc",
|
||||||
|
"pathdiff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openidconnect"
|
name = "openidconnect"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
|
@ -1371,6 +1573,12 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "option-ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "2.10.1"
|
version = "2.10.1"
|
||||||
|
@ -1422,11 +1630,17 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.4.1",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
|
@ -1621,6 +1835,15 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -1630,6 +1853,17 @@ dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"redox_syscall 0.2.16",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.2"
|
version = "1.10.2"
|
||||||
|
@ -1709,10 +1943,12 @@ dependencies = [
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
"winreg",
|
"winreg",
|
||||||
|
@ -2300,6 +2536,15 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -2443,6 +2688,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -2530,6 +2781,19 @@ version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-streams"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
|
|
35
Cargo.toml
35
Cargo.toml
|
@ -1,29 +1,10 @@
|
||||||
[package]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"server",
|
||||||
|
"cli"
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
name = "bin"
|
name = "bin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tokio = { version = "1.33", features = ["full"] }
|
|
||||||
tokio-util = { version="0.7", features = ["io"]}
|
|
||||||
futures-util = "0.3"
|
|
||||||
axum = {version="0.6", features=["macros", "headers", "multipart"]}
|
|
||||||
serde = "1.0"
|
|
||||||
toml = "0.8"
|
|
||||||
duration-str = "0.7.0"
|
|
||||||
render = { git="https://github.com/render-rs/render.rs" }
|
|
||||||
thiserror = "1.0"
|
|
||||||
rand = "0.8"
|
|
||||||
dotenvy = "0.15"
|
|
||||||
markdown = "0.3"
|
|
||||||
axum_oidc = {git="https://git2.zettoit.eu/pfz4/axum_oidc"}
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.10"
|
|
||||||
|
|
||||||
chacha20 = "0.9"
|
|
||||||
sha3 = "0.10"
|
|
||||||
hex = "0.4"
|
|
||||||
bytes = "1.5"
|
|
||||||
pin-project-lite = "0.2"
|
|
||||||
|
|
20
cli/Cargo.toml
Normal file
20
cli/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "binctl"
|
||||||
|
edition = "2021"
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version="4.4", features = ["derive"] }
|
||||||
|
reqwest = { version="0.11", features = ["rustls-tls", "stream"], default-features=false}
|
||||||
|
openidconnect = "3.4"
|
||||||
|
thiserror = "1.0"
|
||||||
|
serde = { version="1.0", features = [ "derive" ] }
|
||||||
|
axum = "0.6"
|
||||||
|
tokio = { version = "1.33", features = ["full"] }
|
||||||
|
open = "5.0"
|
||||||
|
tokio-util = { version="0.7.9", features = ["io"]}
|
||||||
|
dirs = "5.0"
|
||||||
|
confy = "0.5"
|
||||||
|
exitcode = "1.1.2"
|
174
cli/src/auth.rs
Normal file
174
cli/src/auth.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use openidconnect::{
|
||||||
|
core::{CoreAuthenticationFlow, CoreClient, CoreErrorResponseType, CoreProviderMetadata},
|
||||||
|
reqwest::async_http_client,
|
||||||
|
AccessTokenHash, AuthorizationCode, ClaimsVerificationError, ClientId, CsrfToken,
|
||||||
|
DiscoveryError, IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl,
|
||||||
|
RefreshToken, RequestTokenError, Scope, SigningError, StandardErrorResponse, TokenResponse,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("url parse error: {:?}", 0)]
|
||||||
|
UrlParse(#[from] openidconnect::url::ParseError),
|
||||||
|
|
||||||
|
#[error("discovery error: {:?}", 0)]
|
||||||
|
Discovery(#[from] DiscoveryError<openidconnect::reqwest::Error<reqwest::Error>>),
|
||||||
|
|
||||||
|
#[error("request token error: {:?}", 0)]
|
||||||
|
RequestToken(
|
||||||
|
#[from]
|
||||||
|
RequestTokenError<
|
||||||
|
openidconnect::reqwest::Error<reqwest::Error>,
|
||||||
|
StandardErrorResponse<CoreErrorResponseType>,
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
|
||||||
|
#[error("claims verification error: {:?}", 0)]
|
||||||
|
ClaimsVerification(#[from] ClaimsVerificationError),
|
||||||
|
|
||||||
|
#[error("signing error: {:?}", 0)]
|
||||||
|
Signing(#[from] SigningError),
|
||||||
|
|
||||||
|
#[error("server did not return an id token")]
|
||||||
|
NoIdToken,
|
||||||
|
|
||||||
|
#[error("invalid access token")]
|
||||||
|
InvalidAccessToken,
|
||||||
|
|
||||||
|
#[error("no response received")]
|
||||||
|
NoResponse,
|
||||||
|
|
||||||
|
#[error("csrf mismatch")]
|
||||||
|
CsrfMismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ResponseData {
|
||||||
|
pub code: String,
|
||||||
|
pub state: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn login(
|
||||||
|
issuer: &str,
|
||||||
|
client_id: &str,
|
||||||
|
scopes: &[String],
|
||||||
|
refresh_token: &mut Option<String>,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let provider_metadata = CoreProviderMetadata::discover_async(
|
||||||
|
IssuerUrl::new(issuer.to_string())?,
|
||||||
|
async_http_client,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create an OpenID Connect client by specifying the client ID, client secret, authorization URL
|
||||||
|
// and token URL.
|
||||||
|
let client = CoreClient::from_provider_metadata(
|
||||||
|
provider_metadata,
|
||||||
|
ClientId::new(client_id.to_string()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
// Set the URL the user will be redirected to after the authorization process.
|
||||||
|
.set_redirect_uri(RedirectUrl::new("http://[::1]:8080".to_string())?);
|
||||||
|
|
||||||
|
// Generate a PKCE challenge.
|
||||||
|
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
|
||||||
|
|
||||||
|
if let Some(refresh_token) = refresh_token {
|
||||||
|
if let Ok(token_response) = client
|
||||||
|
.exchange_refresh_token(&RefreshToken::new(refresh_token.to_string()))
|
||||||
|
.request_async(async_http_client)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
eprintln!("authenticated with oidc provider");
|
||||||
|
return Ok(token_response.access_token().secret().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the full authorization URL.
|
||||||
|
let mut auth = client.authorize_url(
|
||||||
|
CoreAuthenticationFlow::AuthorizationCode,
|
||||||
|
CsrfToken::new_random,
|
||||||
|
Nonce::new_random,
|
||||||
|
);
|
||||||
|
|
||||||
|
for scope in scopes {
|
||||||
|
auth = auth.add_scope(Scope::new(scope.to_string()));
|
||||||
|
}
|
||||||
|
let (auth_url, csrf_token, nonce) = auth
|
||||||
|
// Set the PKCE code challenge.
|
||||||
|
.set_pkce_challenge(pkce_challenge)
|
||||||
|
.url();
|
||||||
|
open::that(auth_url.to_string()).unwrap();
|
||||||
|
eprintln!("a browser should have been opened with the url {auth_url}. please login with your oidc provider.");
|
||||||
|
|
||||||
|
let (fuse_tx, mut fuse_rx) = mpsc::channel::<ResponseData>(1);
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(handle_post))
|
||||||
|
.with_state(Arc::new(fuse_tx));
|
||||||
|
|
||||||
|
let server = axum::Server::bind(&"[::1]:8080".parse().unwrap()).serve(app.into_make_service());
|
||||||
|
|
||||||
|
let data = tokio::select! {
|
||||||
|
x = fuse_rx.recv() => {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
_ = server => {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = data.ok_or(Error::NoResponse)?;
|
||||||
|
|
||||||
|
// match csrf_state
|
||||||
|
|
||||||
|
if *csrf_token.secret() != data.state {
|
||||||
|
return Err(Error::CsrfMismatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let token_response = client
|
||||||
|
.exchange_code(AuthorizationCode::new(data.code))
|
||||||
|
// Set the PKCE code verifier.
|
||||||
|
.set_pkce_verifier(pkce_verifier)
|
||||||
|
.request_async(async_http_client)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Extract the ID token claims after verifying its authenticity and nonce.
|
||||||
|
let id_token = token_response.id_token().ok_or_else(|| Error::NoIdToken)?;
|
||||||
|
let claims = id_token.claims(&client.id_token_verifier(), &nonce)?;
|
||||||
|
|
||||||
|
// Verify the access token hash to ensure that the access token hasn't been substituted for
|
||||||
|
// another user's.
|
||||||
|
if let Some(expected_access_token_hash) = claims.access_token_hash() {
|
||||||
|
let actual_access_token_hash =
|
||||||
|
AccessTokenHash::from_token(token_response.access_token(), &id_token.signing_alg()?)?;
|
||||||
|
if actual_access_token_hash != *expected_access_token_hash {
|
||||||
|
return Err(Error::InvalidAccessToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_refresh_token) = token_response.refresh_token() {
|
||||||
|
*refresh_token = Some(new_refresh_token.secret().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("authenticated with oidc provider");
|
||||||
|
Ok(token_response.access_token().secret().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_post(
|
||||||
|
State(fuse_tx): State<Arc<mpsc::Sender<ResponseData>>>,
|
||||||
|
Query(data): Query<ResponseData>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
fuse_tx.clone().send(data).await;
|
||||||
|
Html("<html><body>Die Anmeldung war erfolgreich. Du kannst dieses Fenster jetzt schließen.<script>window.close()</script></body></html>")
|
||||||
|
}
|
103
cli/src/main.rs
Normal file
103
cli/src/main.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use reqwest::{Body, Url};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::io::stdin;
|
||||||
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
|
use crate::auth::login;
|
||||||
|
|
||||||
|
mod auth;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub refresh_token: Option<String>,
|
||||||
|
pub binurl: String,
|
||||||
|
pub issuer: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub scopes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[arg(short, long)]
|
||||||
|
content_type: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
ttl: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
refresh_token: None,
|
||||||
|
binurl: "https://bin.zettoit.eu".to_string(),
|
||||||
|
issuer: "https://auth.zettoit.eu/realms/zettoit".to_string(),
|
||||||
|
client_id: "binctl".to_string(),
|
||||||
|
scopes: vec!["zettoit-bin".to_string()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let mut cfg: Config = confy::load("binctl", None).unwrap_or_default();
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
|
let access_token = login(
|
||||||
|
&cfg.issuer,
|
||||||
|
&cfg.client_id,
|
||||||
|
cfg.scopes.as_slice(),
|
||||||
|
&mut cfg.refresh_token,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut bin = create_bin(&cfg.binurl, &access_token).await.unwrap();
|
||||||
|
eprintln!("created bin at {}. uploading...", bin);
|
||||||
|
bin.set_query(args.ttl.map(|x| format!("ttl={}", x)).as_deref());
|
||||||
|
|
||||||
|
upload_to_bin(
|
||||||
|
bin.as_ref(),
|
||||||
|
&args
|
||||||
|
.content_type
|
||||||
|
.unwrap_or("application/octet-stream".to_string()),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let _ = confy::store("binctl", None, cfg);
|
||||||
|
bin.set_query(None);
|
||||||
|
print!("{bin}");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_bin(binurl: &str, access_token: &str) -> Result<Url, reqwest::Error> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
Ok(client
|
||||||
|
.get(binurl)
|
||||||
|
.header("Authorization", format!("Bearer {}", access_token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.url()
|
||||||
|
.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn upload_to_bin(url: &str, content_type: &str) -> Result<(), reqwest::Error> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
if let Err(error) = client
|
||||||
|
.post(url)
|
||||||
|
.header("Content-Type", content_type)
|
||||||
|
.body(Body::wrap_stream(ReaderStream::new(stdin())))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
eprintln!("{:?}", error);
|
||||||
|
std::process::exit(exitcode::TEMPFAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
28
flake.nix
28
flake.nix
|
@ -26,20 +26,22 @@
|
||||||
nixpkgs.lib.genAttrs [
|
nixpkgs.lib.genAttrs [
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
] (system: let
|
] (system: function system nixpkgs.legacyPackages.${system});
|
||||||
|
in rec {
|
||||||
|
packages = forAllSystems(system: syspkgs: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ (import rust-overlay) ];
|
overlays = [ (import rust-overlay) ];
|
||||||
};
|
};
|
||||||
rustToolchain = pkgs.rust-bin.stable.latest.default;
|
rustToolchain = pkgs.rust-bin.stable.latest.default;
|
||||||
|
|
||||||
markdownFilter = path: _type: builtins.match ".*md$" path != null;
|
|
||||||
markdownOrCargo = path: type: (markdownFilter path type) || (craneLib.filterCargoSources path type);
|
|
||||||
|
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
||||||
src = pkgs.lib.cleanSourceWith {
|
src = pkgs.lib.cleanSourceWith {
|
||||||
src = craneLib.path ./.;
|
src = craneLib.path ./.;
|
||||||
filter = markdownOrCargo;
|
filter = path: type:
|
||||||
|
(pkgs.lib.hasSuffix "\.md" path) ||
|
||||||
|
(craneLib.filterCargoSources path type)
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ];
|
nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ];
|
||||||
|
@ -52,18 +54,20 @@
|
||||||
|
|
||||||
bin = craneLib.buildPackage (commonArgs // {
|
bin = craneLib.buildPackage (commonArgs // {
|
||||||
inherit cargoArtifacts;
|
inherit cargoArtifacts;
|
||||||
|
pname = "bin";
|
||||||
});
|
});
|
||||||
in function {
|
binctl = craneLib.buildPackage (commonArgs // {
|
||||||
inherit bin pkgs;
|
inherit cargoArtifacts;
|
||||||
|
pname = "binctl";
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
packages = forAllSystems({pkgs, bin}: {
|
inherit bin binctl;
|
||||||
inherit bin;
|
|
||||||
default = bin;
|
default = bin;
|
||||||
});
|
});
|
||||||
devShells = forAllSystems({pkgs, bin}: pkgs.mkShell {
|
devShells = forAllSystems(system: pkgs: pkgs.mkShell {
|
||||||
inputsFrom = bin;
|
inputsFrom = [packages.${system}.bin packages.${system}.binctl];
|
||||||
});
|
});
|
||||||
hydraJobs."build" = forAllSystems({pkgs, bin}: bin);
|
hydraJobs."bin" = forAllSystems(system: pkgs: packages.${system}.bin);
|
||||||
|
hydraJobs."binctl" = forAllSystems(system: pkgs: packages.${system}.binctl);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
29
server/Cargo.toml
Normal file
29
server/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "bin"
|
||||||
|
version.workspace = true
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.33", features = ["full"] }
|
||||||
|
tokio-util = { version="0.7", features = ["io"]}
|
||||||
|
futures-util = "0.3"
|
||||||
|
axum = {version="0.6", features=["macros", "headers", "multipart"]}
|
||||||
|
serde = "1.0"
|
||||||
|
toml = "0.8"
|
||||||
|
duration-str = "0.7.0"
|
||||||
|
render = { git="https://github.com/render-rs/render.rs" }
|
||||||
|
thiserror = "1.0"
|
||||||
|
rand = "0.8"
|
||||||
|
dotenvy = "0.15"
|
||||||
|
markdown = "0.3"
|
||||||
|
axum_oidc = {git="https://git2.zettoit.eu/pfz4/axum_oidc"}
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.10"
|
||||||
|
|
||||||
|
chacha20 = "0.9"
|
||||||
|
sha3 = "0.10"
|
||||||
|
hex = "0.4"
|
||||||
|
bytes = "1.5"
|
||||||
|
pin-project-lite = "0.2"
|
|
@ -38,6 +38,12 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("invalid ttl")]
|
#[error("invalid ttl")]
|
||||||
InvalidTtl,
|
InvalidTtl,
|
||||||
|
|
||||||
|
#[error("unauthorized")]
|
||||||
|
Unauthorized,
|
||||||
|
|
||||||
|
#[error("forbidden")]
|
||||||
|
Forbidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Error {
|
impl IntoResponse for Error {
|
||||||
|
@ -54,6 +60,8 @@ impl IntoResponse for Error {
|
||||||
(StatusCode::BAD_REQUEST, "invalid multipart data").into_response()
|
(StatusCode::BAD_REQUEST, "invalid multipart data").into_response()
|
||||||
}
|
}
|
||||||
Self::InvalidTtl => (StatusCode::BAD_REQUEST, "invalid ttl specified").into_response(),
|
Self::InvalidTtl => (StatusCode::BAD_REQUEST, "invalid ttl specified").into_response(),
|
||||||
|
Self::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized\n").into_response(),
|
||||||
|
Self::Forbidden => (StatusCode::FORBIDDEN, "forbidden\n").into_response(),
|
||||||
_ => {
|
_ => {
|
||||||
error!("{:?}", self);
|
error!("{:?}", self);
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, "internal server error\n").into_response()
|
(StatusCode::INTERNAL_SERVER_ERROR, "internal server error\n").into_response()
|
|
@ -32,12 +32,12 @@ use chacha20::{
|
||||||
};
|
};
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use garbage_collector::GarbageCollector;
|
use garbage_collector::GarbageCollector;
|
||||||
use log::debug;
|
use log::{debug, warn};
|
||||||
use render::{html, raw};
|
use render::{html, raw};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha3::{Digest, Sha3_256};
|
use sha3::{Digest, Sha3_256};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::{self, File},
|
||||||
io::{AsyncWriteExt, BufReader, BufWriter},
|
io::{AsyncWriteExt, BufReader, BufWriter},
|
||||||
};
|
};
|
||||||
use util::{IdSalt, KeySalt};
|
use util::{IdSalt, KeySalt};
|
||||||
|
@ -147,7 +147,14 @@ async fn main() {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(get_index))
|
.route("/", get(get_index))
|
||||||
.route("/:id", get(get_item).post(post_item).put(post_item))
|
.route(
|
||||||
|
"/:id",
|
||||||
|
get(get_item)
|
||||||
|
.post(upload_bin)
|
||||||
|
.put(upload_bin)
|
||||||
|
.delete(delete_bin),
|
||||||
|
)
|
||||||
|
.route("/:id/delete", get(delete_bin_interactive).post(delete_bin))
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
axum::Server::bind(&"[::]:8080".parse().expect("valid listen address"))
|
axum::Server::bind(&"[::]:8080".parse().expect("valid listen address"))
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
|
@ -195,12 +202,69 @@ async fn get_index(
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_bin(
|
||||||
|
Path(phrase): Path<String>,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
oidc_extractor: Result<OidcExtractor<EmptyAdditionalClaims>, axum_oidc::error::Error>,
|
||||||
|
jwt_claims: Option<Claims<EmptyAdditionalClaims>>,
|
||||||
|
) -> HandlerResult<impl IntoResponse> {
|
||||||
|
let subject = match (oidc_extractor, jwt_claims) {
|
||||||
|
(_, Some(claims)) => claims.sub.to_string(),
|
||||||
|
(Ok(oidc), None) => oidc.claims.subject().to_string(),
|
||||||
|
(Err(_), None) => return Err(Error::Unauthorized),
|
||||||
|
};
|
||||||
|
|
||||||
|
let phrase = Phrase::from_str(&phrase)?;
|
||||||
|
let id = Id::from_phrase(&phrase, &app_state.id_salt);
|
||||||
|
|
||||||
|
let metadata_path = format!("{}/{}.toml", app_state.data, id);
|
||||||
|
let metadata = Metadata::from_file(&metadata_path).await?;
|
||||||
|
|
||||||
|
if metadata.subject != subject {
|
||||||
|
return Err(Error::Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("deleting bin {}", id);
|
||||||
|
let res_meta = fs::remove_file(&format!("{}/{}.toml", app_state.data, id)).await;
|
||||||
|
let res_data = fs::remove_file(&format!("{}/{}.dat", app_state.data, id)).await;
|
||||||
|
|
||||||
|
if res_meta.is_err() || res_data.is_err() {
|
||||||
|
warn!("failed to delete bin {} for manual deletion", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok("ok\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_bin_interactive(
|
||||||
|
_: Path<String>,
|
||||||
|
_: OidcExtractor<EmptyAdditionalClaims>,
|
||||||
|
) -> HandlerResult<impl IntoResponse> {
|
||||||
|
let body = html! {
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{"zettoit bin"}</title>
|
||||||
|
<link rel={"icon"} type={"image/svg"} href={"https://static.zettoit.eu/img/zettoit-logo.svg"}/>
|
||||||
|
</head>
|
||||||
|
<body style={"font-family: monospace; background-color: black; color: white;"}>
|
||||||
|
<div style={"margin: auto; max-width: 80ch;"}>
|
||||||
|
<h2>{"Confirm Deletion"}</h2>
|
||||||
|
<p>{"The bin will be deleted. All data will be permanently lost."}</p>
|
||||||
|
<form method={"post"}>
|
||||||
|
<button type={"submit"}>{"Delete"}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
};
|
||||||
|
Ok(Html(body))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PostQuery {
|
pub struct PostQuery {
|
||||||
ttl: Option<String>,
|
ttl: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_item(
|
async fn upload_bin(
|
||||||
Path(phrase): Path<String>,
|
Path(phrase): Path<String>,
|
||||||
Query(params): Query<PostQuery>,
|
Query(params): Query<PostQuery>,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
|
@ -308,7 +372,6 @@ async fn post_item(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
|
||||||
async fn get_item(
|
async fn get_item(
|
||||||
Path(phrase): Path<String>,
|
Path(phrase): Path<String>,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
Loading…
Reference in a new issue