allow website content to be updated
This commit is contained in:
parent
a5ffb69ccd
commit
f4b45cb2f8
14 changed files with 405 additions and 38 deletions
47
Cargo.lock
generated
47
Cargo.lock
generated
|
@ -702,9 +702,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "b64-ct"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32261e5a95e234446dd31774ef4305dc164039370fe2622fd3e935a0f2730689"
|
||||
checksum = "41dd1e07aafe656df62d40e74d4627a0cce26da43b0bec233ac1305207a5a768"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
|
@ -962,9 +962,13 @@ dependencies = [
|
|||
"chir-rs-http-api",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"dotenvy",
|
||||
"eyre",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
|
@ -1028,10 +1032,13 @@ dependencies = [
|
|||
"chir-rs-misc",
|
||||
"chrono",
|
||||
"eyre",
|
||||
"futures",
|
||||
"mime",
|
||||
"rusty_paseto",
|
||||
"sentry-tower",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.6.2",
|
||||
"tracing",
|
||||
"unicode-normalization",
|
||||
|
@ -2482,6 +2489,16 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
|
@ -3064,10 +3081,12 @@ dependencies = [
|
|||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"windows-registry",
|
||||
|
@ -4170,12 +4189,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.12"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
||||
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
|
@ -4408,6 +4428,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.17"
|
||||
|
@ -4626,6 +4652,19 @@ version = "0.2.97"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.74"
|
||||
|
|
167
Cargo.nix
167
Cargo.nix
|
@ -32,7 +32,7 @@ args@{
|
|||
ignoreLockHash,
|
||||
}:
|
||||
let
|
||||
nixifiedLockHash = "7dc55944ae2b0d1b65a004e3be3f5314339c3d2e023e794d54b030b197c5bf87";
|
||||
nixifiedLockHash = "51a600afd0b9503f1748762462902306933599d46304da01c1ecdecdb6049697";
|
||||
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
|
||||
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
|
||||
lockHashIgnored =
|
||||
|
@ -1927,7 +1927,7 @@ else
|
|||
inherit profileName;
|
||||
}).out;
|
||||
tokio_util =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.12" {
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
};
|
||||
|
@ -2262,15 +2262,15 @@ else
|
|||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.2" =
|
||||
"registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.3" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
name = "b64-ct";
|
||||
version = "0.1.2";
|
||||
version = "0.1.3";
|
||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
src = fetchCratesIo {
|
||||
inherit name version;
|
||||
sha256 = "32261e5a95e234446dd31774ef4305dc164039370fe2622fd3e935a0f2730689";
|
||||
sha256 = "41dd1e07aafe656df62d40e74d4627a0cce26da43b0bec233ac1305207a5a768";
|
||||
};
|
||||
features = builtins.concatLists [
|
||||
[ "default" ]
|
||||
|
@ -3010,10 +3010,22 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".color-eyre."0.6.3" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
dotenvy =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".dotenvy."0.15.7" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
eyre =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".eyre."0.6.12" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
mime =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".mime."0.3.17" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
mime_guess =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".mime_guess."2.0.5" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
reqwest =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".reqwest."0.12.9" {
|
||||
inherit profileName;
|
||||
|
@ -3022,6 +3034,10 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.42.0" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tracing =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.41" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tracing_subscriber =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing-subscriber."0.3.19" {
|
||||
inherit profileName;
|
||||
|
@ -3164,7 +3180,7 @@ else
|
|||
inherit profileName;
|
||||
}).out;
|
||||
b64_ct =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.2" {
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.3" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
bincode =
|
||||
|
@ -3184,6 +3200,10 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".eyre."0.6.12" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
futures =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.31" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
mime =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".mime."0.3.17" {
|
||||
inherit profileName;
|
||||
|
@ -3196,10 +3216,18 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".sentry-tower."0.34.0" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
serde =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde."1.0.215" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tokio =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.42.0" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tokio_util =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tower_http =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-http."0.6.2" {
|
||||
inherit profileName;
|
||||
|
@ -3226,7 +3254,6 @@ else
|
|||
[ "axum-core" ]
|
||||
[ "bytes" ]
|
||||
[ "mime" ]
|
||||
[ "tracing" ]
|
||||
];
|
||||
dependencies = {
|
||||
async_trait =
|
||||
|
@ -3238,7 +3265,7 @@ else
|
|||
inherit profileName;
|
||||
}).out;
|
||||
b64_ct =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.2" {
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".b64-ct."0.1.3" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
bincode =
|
||||
|
@ -5111,6 +5138,7 @@ else
|
|||
[ "async-await" ]
|
||||
[ "async-await-macro" ]
|
||||
[ "channel" ]
|
||||
[ "default" ]
|
||||
[ "futures-channel" ]
|
||||
[ "futures-io" ]
|
||||
[ "futures-macro" ]
|
||||
|
@ -5351,7 +5379,7 @@ else
|
|||
inherit profileName;
|
||||
}).out;
|
||||
tokio_util =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.12" {
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tracing =
|
||||
|
@ -5409,7 +5437,7 @@ else
|
|||
inherit profileName;
|
||||
}).out;
|
||||
tokio_util =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.12" {
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tracing =
|
||||
|
@ -7269,6 +7297,38 @@ else
|
|||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".mime_guess."2.0.5" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
name = "mime_guess";
|
||||
version = "2.0.5";
|
||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
src = fetchCratesIo {
|
||||
inherit name version;
|
||||
sha256 = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e";
|
||||
};
|
||||
features = builtins.concatLists [
|
||||
[ "default" ]
|
||||
[ "rev-mappings" ]
|
||||
];
|
||||
dependencies = {
|
||||
mime =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".mime."0.3.17" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
unicase =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".unicase."2.8.0" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
};
|
||||
buildDependencies = {
|
||||
unicase =
|
||||
(buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".unicase."2.8.0" {
|
||||
profileName = "__noProfile";
|
||||
}).out;
|
||||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".minimal-lexical."0.2.1" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
|
@ -8815,6 +8875,7 @@ else
|
|||
[ "rustls-tls" ]
|
||||
[ "rustls-tls-webpki-roots" ]
|
||||
[ "rustls-tls-webpki-roots-no-provider" ]
|
||||
[ "stream" ]
|
||||
];
|
||||
dependencies = {
|
||||
base64 =
|
||||
|
@ -8929,6 +8990,10 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-rustls."0.26.0" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
${if !(hostPlatform.parsed.cpu.name == "wasm32") then "tokio_util" else null} =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
tower_service =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".tower-service."0.3.3" {
|
||||
inherit profileName;
|
||||
|
@ -8945,6 +9010,10 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-futures."0.4.47"
|
||||
{ inherit profileName; }
|
||||
).out;
|
||||
${if hostPlatform.parsed.cpu.name == "wasm32" then "wasm_streams" else null} =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-streams."0.4.2" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
${if hostPlatform.parsed.cpu.name == "wasm32" then "web_sys" else null} =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.74" {
|
||||
inherit profileName;
|
||||
|
@ -12344,19 +12413,21 @@ else
|
|||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.12" =
|
||||
"registry+https://github.com/rust-lang/crates.io-index".tokio-util."0.7.13" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
name = "tokio-util";
|
||||
version = "0.7.12";
|
||||
version = "0.7.13";
|
||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
src = fetchCratesIo {
|
||||
inherit name version;
|
||||
sha256 = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a";
|
||||
sha256 = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078";
|
||||
};
|
||||
features = builtins.concatLists [
|
||||
[ "codec" ]
|
||||
[ "compat" ]
|
||||
[ "default" ]
|
||||
[ "futures-io" ]
|
||||
[ "io" ]
|
||||
];
|
||||
dependencies = {
|
||||
|
@ -12368,6 +12439,10 @@ else
|
|||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-core."0.3.31" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
futures_io =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-io."0.3.31" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
futures_sink =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-sink."0.3.31" {
|
||||
inherit profileName;
|
||||
|
@ -13030,6 +13105,18 @@ else
|
|||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".unicase."2.8.0" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
name = "unicase";
|
||||
version = "2.8.0";
|
||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
src = fetchCratesIo {
|
||||
inherit name version;
|
||||
sha256 = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df";
|
||||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".unicode-bidi."0.3.17" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
|
@ -13590,6 +13677,40 @@ else
|
|||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".wasm-streams."0.4.2" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
name = "wasm-streams";
|
||||
version = "0.4.2";
|
||||
registry = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
src = fetchCratesIo {
|
||||
inherit name version;
|
||||
sha256 = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65";
|
||||
};
|
||||
dependencies = {
|
||||
futures_util =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.31" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
js_sys =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".js-sys."0.3.74" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
wasm_bindgen =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen."0.2.97" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
wasm_bindgen_futures =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".wasm-bindgen-futures."0.4.47"
|
||||
{ inherit profileName; }
|
||||
).out;
|
||||
web_sys =
|
||||
(rustPackages."registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.74" {
|
||||
inherit profileName;
|
||||
}).out;
|
||||
};
|
||||
});
|
||||
|
||||
"registry+https://github.com/rust-lang/crates.io-index".web-sys."0.3.74" =
|
||||
overridableMkRustCrate
|
||||
(profileName: rec {
|
||||
|
@ -13612,16 +13733,36 @@ else
|
|||
[ "Headers" ]
|
||||
[ "MessageEvent" ]
|
||||
[ "Performance" ]
|
||||
[ "QueuingStrategy" ]
|
||||
[ "ReadableByteStreamController" ]
|
||||
[ "ReadableStream" ]
|
||||
[ "ReadableStreamByobReader" ]
|
||||
[ "ReadableStreamByobRequest" ]
|
||||
[ "ReadableStreamDefaultController" ]
|
||||
[ "ReadableStreamDefaultReader" ]
|
||||
[ "ReadableStreamGetReaderOptions" ]
|
||||
[ "ReadableStreamReadResult" ]
|
||||
[ "ReadableStreamReaderMode" ]
|
||||
[ "ReadableStreamType" ]
|
||||
[ "ReadableWritablePair" ]
|
||||
[ "Request" ]
|
||||
[ "RequestCredentials" ]
|
||||
[ "RequestInit" ]
|
||||
[ "RequestMode" ]
|
||||
[ "Response" ]
|
||||
[ "ServiceWorkerGlobalScope" ]
|
||||
[ "StreamPipeOptions" ]
|
||||
[ "TransformStream" ]
|
||||
[ "TransformStreamDefaultController" ]
|
||||
[ "Transformer" ]
|
||||
[ "UnderlyingSink" ]
|
||||
[ "UnderlyingSource" ]
|
||||
[ "Window" ]
|
||||
[ "Worker" ]
|
||||
[ "WorkerGlobalScope" ]
|
||||
[ "WritableStream" ]
|
||||
[ "WritableStreamDefaultController" ]
|
||||
[ "WritableStreamDefaultWriter" ]
|
||||
[ "default" ]
|
||||
[ "std" ]
|
||||
];
|
||||
|
|
|
@ -95,9 +95,9 @@ impl CaStore {
|
|||
{
|
||||
let mut reader = Box::pin(reader);
|
||||
let string_id = lexicographic_base64::encode(id.to_be_bytes());
|
||||
|
||||
info!("Starting multipart upload {id}");
|
||||
let source_fname = format!("temp/{string_id}");
|
||||
|
||||
/*info!("Starting multipart upload {id}");
|
||||
let multipart_result = self
|
||||
.client
|
||||
.create_multipart_upload()
|
||||
|
@ -163,7 +163,28 @@ impl CaStore {
|
|||
.set_upload_id(multipart_result.upload_id)
|
||||
.send()
|
||||
.await
|
||||
.context("Completing multipart upload")?;
|
||||
.context("Completing multipart upload")?;*/
|
||||
|
||||
let hasher = Arc::new(Mutex::new(Hasher::new()));
|
||||
let mut buf = Vec::new();
|
||||
reader.read_to_end(&mut buf).await?;
|
||||
let buf = Bytes::from(buf);
|
||||
let buf2 = buf.clone();
|
||||
let hasher2 = Arc::clone(&hasher);
|
||||
spawn_blocking(move || {
|
||||
hasher2.blocking_lock().update_rayon(&buf2);
|
||||
})
|
||||
.await?;
|
||||
self.client
|
||||
.put_object()
|
||||
.bucket(&*self.bucket)
|
||||
.key(&source_fname)
|
||||
.body(ByteStream::from(buf.to_vec()))
|
||||
.send()
|
||||
.await
|
||||
.context("Uploading file")?;
|
||||
|
||||
let hash = hasher.lock().await.finalize();
|
||||
|
||||
let target_fname = lexicographic_base64::encode(hash.as_bytes());
|
||||
|
||||
|
|
|
@ -8,9 +8,11 @@ bincode = "2.0.0-rc.3"
|
|||
chir-rs-http-api = { version = "0.1.0", path = "../chir-rs-http-api" }
|
||||
clap = { version = "4.5.21", features = ["derive"] }
|
||||
color-eyre = "0.6.3"
|
||||
dotenvy = "0.15.7"
|
||||
eyre = "0.6.12"
|
||||
reqwest = { version = "0.12.9", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
] }
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
|
||||
mime = "0.3.17"
|
||||
mime_guess = "2.0.5"
|
||||
reqwest = { version = "0.12.9", default-features = false, features = ["rustls-tls", "stream"] }
|
||||
tokio = { version = "1.41.1", features = ["fs", "macros", "rt-multi-thread"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, future::Future, path::Path, pin::Pin};
|
||||
|
||||
use chir_rs_http_api::{
|
||||
auth::{LoginRequest, PasetoToken, Scope},
|
||||
errors::APIError,
|
||||
};
|
||||
use clap::{arg, Parser, Subcommand};
|
||||
use eyre::Result;
|
||||
use eyre::{eyre, Context as _, OptionExt as _, Result};
|
||||
use mime_guess::{Mime, MimeGuess};
|
||||
use reqwest::Body;
|
||||
use tokio::join;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
|
@ -24,6 +28,18 @@ enum Command {
|
|||
#[arg(short, long)]
|
||||
password: String,
|
||||
},
|
||||
Upload {
|
||||
#[arg(short, long)]
|
||||
source: String,
|
||||
#[arg(short, long)]
|
||||
dest: String,
|
||||
},
|
||||
UploadDir {
|
||||
#[arg(short, long)]
|
||||
source: String,
|
||||
#[arg(short, long)]
|
||||
dest: String,
|
||||
},
|
||||
}
|
||||
|
||||
async fn login(url: String, username: String, password: String) -> Result<()> {
|
||||
|
@ -58,14 +74,76 @@ async fn login(url: String, username: String, password: String) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(source))]
|
||||
async fn upload(url: String, source: impl AsRef<Path>, dest: String) -> Result<()> {
|
||||
let client = reqwest::Client::new();
|
||||
let token = std::env::var("CHIR_RS_TOKEN")?;
|
||||
let file = tokio::fs::File::open(&source).await?;
|
||||
let res = client
|
||||
.post(format!("{url}{dest}"))
|
||||
.header(
|
||||
"Content-Type",
|
||||
MimeGuess::from_path(&source)
|
||||
.first()
|
||||
.unwrap_or(mime::APPLICATION_OCTET_STREAM)
|
||||
.to_string(),
|
||||
)
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.body(Body::from(file))
|
||||
.send()
|
||||
.await?;
|
||||
if !res.status().is_success() {
|
||||
let response = res.bytes().await?;
|
||||
let response: APIError =
|
||||
bincode::decode_from_slice(&response, bincode::config::standard())?.0;
|
||||
Err(response).with_context(|| format!("Uploading to {dest}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(source))]
|
||||
async fn upload_dir(url: String, source: impl AsRef<Path>, dest: String) -> Result<()> {
|
||||
let mut dir = tokio::fs::read_dir(source).await?;
|
||||
while let Some(ent) = dir.next_entry().await? {
|
||||
let file_type = ent.file_type().await?;
|
||||
let file_name_str = ent
|
||||
.file_name()
|
||||
.into_string()
|
||||
.map_err(|_| eyre!("Invalid file name encountered"))?;
|
||||
let tgt = if dest.is_empty() {
|
||||
file_name_str.clone()
|
||||
} else {
|
||||
format!("{dest}/{file_name_str}")
|
||||
};
|
||||
if file_type.is_dir() {
|
||||
let sub_fut: Pin<Box<dyn Future<Output = Result<()>>>> =
|
||||
Box::pin(upload_dir(url.clone(), ent.path(), tgt));
|
||||
sub_fut.await?;
|
||||
continue;
|
||||
}
|
||||
if file_name_str == "index.html" {
|
||||
if dest.is_empty() {
|
||||
upload(url.clone(), ent.path(), "".to_string()).await?;
|
||||
} else {
|
||||
upload(url.clone(), ent.path(), format!("{dest}/")).await?;
|
||||
}
|
||||
}
|
||||
upload(url.clone(), ent.path(), tgt).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install().ok();
|
||||
dotenvy::dotenv().ok();
|
||||
tracing_subscriber::fmt::init();
|
||||
let matches = Args::parse();
|
||||
|
||||
match matches.command {
|
||||
Command::Login { username, password } => login(matches.url, username, password).await?,
|
||||
Command::Upload { source, dest } => upload(matches.url, source, dest).await?,
|
||||
Command::UploadDir { source, dest } => upload_dir(matches.url, source, dest).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -165,11 +165,11 @@ impl File {
|
|||
/// Creates a new file
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if a database error occurs when writing, or there is a conflict
|
||||
/// This function returns an error if a database error occurs when writing
|
||||
#[instrument(skip(db))]
|
||||
pub async fn new(db: &Database, path: &str, mime: &str, hash: &Hash) -> Result<()> {
|
||||
query_as(
|
||||
r#"INSERT INTO file_map ("file_path", "mime", "b3hash") VALUES ($1, $2, $3) RETURNING *"#,
|
||||
r#"INSERT INTO file_map ("file_path", "mime", "b3hash") VALUES ($1, $2, $3) ON CONFLICT ("file_path", "mime") DO UPDATE SET "b3hash" = $3 RETURNING *"#,
|
||||
)
|
||||
.bind(path)
|
||||
.bind(mime)
|
||||
|
|
|
@ -23,7 +23,7 @@ pub async fn expire(db: &Database) -> Result<()> {
|
|||
let oldest_acceptable_id = oldest_acceptable_id.to_be_bytes();
|
||||
#[expect(clippy::panic, reason = "sqlx moment")]
|
||||
query!(
|
||||
r#"DELETE FROM "session_scopes" WHERE session_id < $1"#,
|
||||
r#"DELETE FROM "sessions" WHERE id < $1"#,
|
||||
&oldest_acceptable_id
|
||||
)
|
||||
.execute(&*db.0)
|
||||
|
|
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[features]
|
||||
axum = ["axum-core", "async-trait", "bytes", "mime", "tracing"]
|
||||
axum = ["axum-core", "async-trait", "bytes", "mime"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { version = "0.1.83", optional = true }
|
||||
|
@ -18,7 +18,7 @@ http = "1.1.0"
|
|||
mime = { version = "0.3.17", optional = true }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
thiserror = "2.0.3"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
tracing = { version = "0.1.40" }
|
||||
|
||||
[lints.rust]
|
||||
deprecated-safe = "forbid"
|
||||
|
|
|
@ -4,13 +4,27 @@ use bincode::{Decode, Encode};
|
|||
use educe::Educe;
|
||||
use eyre::{bail, eyre, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, fmt::Debug};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
|
||||
/// List of supported scopes for authentication
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode, Hash)]
|
||||
pub enum Scope {
|
||||
/// Full scope granted by logging in.
|
||||
Full,
|
||||
/// The ability to create or update files.
|
||||
CreateUpdateFile,
|
||||
}
|
||||
|
||||
impl Display for Scope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Self::Full => write!(f, "Full permissions"),
|
||||
Self::CreateUpdateFile => write!(f, "Create and update files"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
|
@ -19,6 +33,7 @@ impl Scope {
|
|||
pub const fn to_i64(self) -> i64 {
|
||||
match self {
|
||||
Self::Full => 0,
|
||||
Self::CreateUpdateFile => 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +44,7 @@ impl Scope {
|
|||
pub fn from_i64(id: i64) -> Result<Self> {
|
||||
match id {
|
||||
0 => Ok(Self::Full),
|
||||
1 => Ok(Self::CreateUpdateFile),
|
||||
_ => bail!("Invalid scope ID {id}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
use bincode::{Decode, Encode};
|
||||
use http::StatusCode;
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
use crate::auth::Scope;
|
||||
|
||||
/// The main error type
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, Error)]
|
||||
|
@ -31,7 +34,7 @@ pub enum APIError {
|
|||
#[error("Invalid payload")]
|
||||
PayloadInvalid,
|
||||
/// Returned when the error is unknown
|
||||
#[error("Unknown Error")]
|
||||
#[error("Unknown Error {0}")]
|
||||
Unknown(String),
|
||||
/// Returned when there is a database error
|
||||
#[error("Database error: {0}")]
|
||||
|
@ -57,6 +60,16 @@ pub enum APIError {
|
|||
/// Invalid session
|
||||
#[error("Invalid session")]
|
||||
InvalidSession,
|
||||
/// Missing scope
|
||||
#[error("Missing required scope for request: {0}")]
|
||||
MissingScope(Scope),
|
||||
}
|
||||
|
||||
impl From<eyre::Report> for APIError {
|
||||
fn from(value: eyre::Report) -> Self {
|
||||
error!("Error while handling request: {value:?}");
|
||||
Self::Unknown(format!("Error while handling request: {value:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl APIError {
|
||||
|
@ -75,7 +88,8 @@ impl APIError {
|
|||
| Self::InvalidAuthorizationHeader(_)
|
||||
| Self::InvalidAuthorizationMethod(_, _)
|
||||
| Self::Unauthorized
|
||||
| Self::InvalidSession => StatusCode::UNAUTHORIZED,
|
||||
| Self::InvalidSession
|
||||
| Self::MissingScope(_) => StatusCode::UNAUTHORIZED,
|
||||
Self::Unknown(_) | Self::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,13 @@ chir-rs-misc = { version = "0.1.0", path = "../chir-rs-misc", features = [
|
|||
] }
|
||||
chrono = "0.4.38"
|
||||
eyre = "0.6.12"
|
||||
futures = "0.3.31"
|
||||
mime = "0.3.17"
|
||||
rusty_paseto = { version = "0.7.1", default-features = false, features = ["batteries_included", "v4_local"] }
|
||||
sentry-tower = { version = "0.34.0", features = ["axum", "axum-matched-path"] }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
tokio = { version = "1.41.1", features = ["fs", "net"] }
|
||||
tokio-util = { version = "0.7.13", features = ["compat"] }
|
||||
tower-http = { version = "0.6.2", features = ["trace"] }
|
||||
tracing = "0.1.40"
|
||||
unicode-normalization = "0.1.24"
|
||||
|
|
|
@ -20,6 +20,23 @@ use crate::AppState;
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AuthHeader(pub String, pub HashSet<Scope>);
|
||||
|
||||
impl AuthHeader {
|
||||
/// Checks whether or not a scope is granted for the session
|
||||
///
|
||||
/// # Errors
|
||||
/// This function returns an error if the current session does not have a scope granted.
|
||||
pub fn assert_scope(&self, scope: Scope) -> Result<(), APIError> {
|
||||
if self.1.contains(&Scope::Full) {
|
||||
return Ok(());
|
||||
}
|
||||
if self.1.contains(&scope) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(APIError::MissingScope(scope))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FromRequestParts<AppState> for AuthHeader {
|
||||
type Rejection = APIError;
|
||||
|
|
|
@ -2,20 +2,24 @@
|
|||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
extract::{Query, State},
|
||||
http::{
|
||||
header::{ACCEPT, CACHE_CONTROL, CONTENT_LENGTH, CONTENT_TYPE, ETAG, IF_NONE_MATCH},
|
||||
HeaderMap, StatusCode, Uri,
|
||||
HeaderMap, Request, StatusCode, Uri,
|
||||
},
|
||||
response::Response,
|
||||
};
|
||||
use chir_rs_db::file::File;
|
||||
use chir_rs_http_api::{auth::Scope, errors::APIError};
|
||||
use chir_rs_misc::lexicographic_base64;
|
||||
use eyre::Context as _;
|
||||
use futures::TryStreamExt;
|
||||
use mime::MimeIter;
|
||||
use serde::Deserialize;
|
||||
use tokio_util::compat::FuturesAsyncReadCompatExt as _;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use crate::AppState;
|
||||
use crate::{auth::req_auth::auth_header::AuthHeader, AppState};
|
||||
|
||||
/// Formats an eyre error message
|
||||
#[expect(clippy::expect_used, reason = "fatal error in error handling function")]
|
||||
|
@ -153,3 +157,35 @@ pub async fn serve_files(
|
|||
.with_context(|| format!("Serving file for {path}"))
|
||||
.map_err(format_error)
|
||||
}
|
||||
|
||||
/// Creates a static file
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if the request fails.
|
||||
pub async fn create_files(
|
||||
State(state): State<AppState>,
|
||||
session: AuthHeader,
|
||||
uri: Uri,
|
||||
headers: HeaderMap,
|
||||
req: Request<Body>,
|
||||
) -> Result<[u8; 32], APIError> {
|
||||
session.assert_scope(Scope::CreateUpdateFile)?;
|
||||
let mime = headers
|
||||
.get(CONTENT_TYPE)
|
||||
.ok_or_else(|| APIError::ClientMissingContentType {
|
||||
expected: "*/*".to_string(),
|
||||
})?
|
||||
.to_str()
|
||||
.map_err(|e| APIError::ClientInvalidContentType {
|
||||
expected: "*/*".to_string(),
|
||||
received: format!("{e:?}"),
|
||||
})?;
|
||||
let mut stream =
|
||||
TryStreamExt::map_err(req.into_body().into_data_stream(), std::io::Error::other)
|
||||
.into_async_read()
|
||||
.compat();
|
||||
let result = state.ca.upload(&mut stream).await?;
|
||||
File::new(&state.db, uri.path(), mime, &result).await?;
|
||||
Ok(*result.as_bytes())
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ pub async fn main(cfg: Arc<ChirRs>, db: Database, castore: CaStore) -> Result<()
|
|||
get(|| async move { metric_handle.render() }),
|
||||
)
|
||||
.route("/.api/auth/login", post(auth::password_login::login))
|
||||
.fallback(get(ca_server::serve_files))
|
||||
.fallback(get(ca_server::serve_files).post(ca_server::create_files))
|
||||
.with_state(AppState {
|
||||
db,
|
||||
ca: castore,
|
||||
|
|
Loading…
Reference in a new issue