diff --git a/Cargo.lock b/Cargo.lock index 47b48cf..3825d76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,30 +19,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.5.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "crypto-common", "generic-array", ] [[package]] name = "aes" -version = "0.8.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.12", + "opaque-debug", ] [[package]] name = "aes-gcm" -version = "0.10.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", "aes", @@ -54,11 +54,22 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.9" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "getrandom", "once_cell", "version_check", @@ -103,9 +114,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -149,26 +160,11 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "anyhow" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" -dependencies = [ - "backtrace", -] - [[package]] name = "arc-swap" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" - -[[package]] -name = "arcstr" -version = "1.1.5" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e" +checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" [[package]] name = "argon2" @@ -178,33 +174,15 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.12", "password-hash", ] -[[package]] -name = "argon2-kdf" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d94b777d02c9d2eb20328e5b045abfdeb128739f98f3fe77fdaa8a3ead6b7a" -dependencies = [ - "base64 0.21.7", - "bindgen", - "cc", - "rand", -] - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "askama" @@ -214,16 +192,14 @@ checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" dependencies = [ "askama_derive", "askama_escape", - "humansize", - "num-traits", "percent-encoding", ] [[package]] name = "askama_axum" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b336dea26a2eb67f04e1134385721f794b654a870ce5146d2fcb69ea39c3a4" +checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163" dependencies = [ "askama", "axum-core", @@ -237,13 +213,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" dependencies = [ "askama_parser", - "basic-toml", "mime", "mime_guess", "proc-macro2", "quote", - "serde", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -261,188 +235,6 @@ dependencies = [ "nom", ] -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" -dependencies = [ - "concurrent-queue", - "event-listener 5.1.0", - "event-listener-strategy 0.5.0", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" -dependencies = [ - "async-lock 3.3.0", - "async-task", - "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.2.0", - "slab", -] - -[[package]] -name = "async-fred-session" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0041afbf24aedf0cbae7c71f0cf7f0536e90ba68fa3fd88998ea9649fb3c4afa" -dependencies = [ - "async-session", - "fred", - "futures", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.2.0", - "async-executor", - "async-io 2.3.1", - "async-lock 3.3.0", - "blocking", - "futures-lite 2.2.0", - "once_cell", - "tokio", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" -dependencies = [ - "async-lock 3.3.0", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-io", - "futures-lite 2.2.0", - "parking", - "polling 3.5.0", - "rustix 0.38.31", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", - "pin-project-lite", -] - -[[package]] -name = "async-session" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da4ce523b4e2ebaaf330746761df23a465b951a83d84bbce4233dabedae630" -dependencies = [ - "anyhow", - "async-lock 2.8.0", - "async-trait", - "base64 0.13.1", - "bincode", - "blake3", - "chrono", - "hmac 0.11.0", - "log", - "rand", - "serde", - "serde_json", - "sha2 0.9.9", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 1.13.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.5" @@ -462,15 +254,9 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] -[[package]] -name = "async-task" -version = "4.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" - [[package]] name = "async-trait" version = "0.1.77" @@ -479,7 +265,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -491,17 +277,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "atomic-write-file" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b" dependencies = [ "nix", "rand", @@ -515,20 +295,20 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" dependencies = [ "async-trait", "axum-core", "axum-macros", - "bitflags 1.3.2", "bytes", "futures-util", - "headers", "http", "http-body", + "http-body-util", "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -537,22 +317,19 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "serde_json", - "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", "tower", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "axum-client-ip" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef117890a418b7832678d9ea1e1c08456dd7b2fd1dadb9676cd6f0fe7eb4b21" +checksum = "5e7c467bdcd2bd982ce5c8742a1a178aba7b03db399fd18f5d5d438f5aa91cb4" dependencies = [ "axum", "forwarded-header-value", @@ -561,111 +338,70 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", "bytes", "futures-util", "http", "http-body", - "mime", - "rustversion", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-extra" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e433be9382c737320af3924f7d5fc6f89c155cf2bf88949d8f5126fab283f" -dependencies = [ - "axum", - "axum-core", - "bytes", - "cookie", - "futures-util", - "http", - "http-body", + "http-body-util", "mime", "pin-project-lite", - "serde", - "tokio", - "tower", + "rustversion", + "sync_wrapper", "tower-layer", "tower-service", ] [[package]] name = "axum-login" -version = "0.5.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2160b4bfd1db39feb3e689c287519d4b9234a5bcbdf89e975027b04d25c4bc32" +checksum = "bf7d8260dd72d25b5d97f47a0ac6436fb7d07237ff6df10620ee21351de7df5a" dependencies = [ "async-trait", "axum", - "axum-sessions", - "base64 0.13.1", - "dyn-clone", - "eyre", - "futures", + "form_urlencoded", "ring", - "secrecy", "serde", - "serde_json", - "tokio", - "tower", - "tower-http 0.3.5", + "thiserror", + "tower-cookies", + "tower-layer", + "tower-service", + "tower-sessions", "tracing", + "urlencoding", ] [[package]] name = "axum-macros" -version = "0.3.8" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] -name = "axum-sessions" +name = "axum-messages" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714cad544cd87d8da821cda715bb9aaa5d4d1adbdb64c549b18138e3cbf93c44" -dependencies = [ - "async-session", - "axum", - "axum-extra", - "futures", - "http-body", - "tokio", - "tower", - "tracing", -] - -[[package]] -name = "axum_csrf" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362fcef4c28a7834c5c2e0b021b5a538a5d16a5be8c543edbf9e18e1d2206115" +checksum = "6c7f1cec4bcd7289b88a1d59af0014f53460fa2ccaa421b080b1a1743a75343e" dependencies = [ - "argon2", "async-trait", "axum-core", - "cookie", "http", - "rand", - "thiserror", - "time", - "tower-layer", - "tower-service", + "parking_lot", + "serde", + "serde_json", + "tower", + "tower-sessions-core", ] [[package]] @@ -676,7 +412,7 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -685,15 +421,15 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "base64ct" @@ -702,44 +438,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "basic-toml" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.69.4" +name = "bigdecimal" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" dependencies = [ - "bitflags 2.4.2", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.50", - "which", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] @@ -758,27 +464,24 @@ dependencies = [ ] [[package]] -name = "blake2" -version = "0.10.6" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "digest 0.10.7", + "funty", + "radium", + "tap", + "wyz", ] [[package]] -name = "blake3" -version = "0.3.8" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if 0.1.10", - "constant_time_eq", - "crypto-mac 0.8.0", - "digest 0.9.0", + "digest 0.10.7", ] [[package]] @@ -800,26 +503,56 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.5.1" +name = "borsh" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.52", + "syn_derive", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytecheck" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ - "async-channel 2.2.0", - "async-lock 3.3.0", - "async-task", - "fastrand 2.0.1", - "futures-io", - "futures-lite 2.2.0", - "piper", - "tracing", + "bytecheck_derive", + "ptr_meta", + "simdutf8", ] [[package]] -name = "bumpalo" -version = "3.15.1" +name = "bytecheck_derive" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "byteorder" @@ -845,74 +578,76 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "jobserver", - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] -name = "cexpr" -version = "0.6.0" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "cfg-if" -version = "0.1.10" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "chacha20" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.52.0", + "wasm-bindgen", + "windows-targets 0.52.4", ] [[package]] name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.7.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "glob", - "libc", - "libloading", + "generic-array", ] [[package]] name = "clap" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" dependencies = [ "clap_builder", "clap_derive", @@ -920,14 +655,16 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", + "unicase", + "unicode-width", ] [[package]] @@ -939,7 +676,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -954,40 +691,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "concurrent-queue" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "cookie" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" dependencies = [ - "aes-gcm", - "base64 0.21.7", - "hmac 0.12.1", "percent-encoding", - "rand", - "sha2 0.10.8", - "subtle", "time", "version_check", ] @@ -1014,6 +730,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -1066,39 +791,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", "typenum", ] [[package]] name = "crypto-mac" -version = "0.8.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array", "subtle", ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "csrf" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "b0257f83673d49bb1f149d923cb923f7d5a270248d64fdb6e847190935ecf1e0" dependencies = [ + "aead", + "aes-gcm", + "byteorder", + "chacha20poly1305", + "chrono", + "data-encoding", "generic-array", - "subtle", + "hmac 0.11.0", + "log", + "rand", + "sha2 0.9.9", ] [[package]] name = "ctr" -version = "0.9.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ "cipher", ] +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.52", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "der" version = "0.7.8" @@ -1117,6 +891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1157,12 +932,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "dyn-clone" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" - [[package]] name = "either" version = "1.10.0" @@ -1210,7 +979,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "home", "windows-sys 0.48.0", ] @@ -1221,67 +990,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" -dependencies = [ - "event-listener 5.1.0", - "pin-project-lite", -] - -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.0.1" @@ -1356,16 +1064,14 @@ dependencies = [ [[package]] name = "fred" -version = "6.3.2" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15cc18b56395b8b15ffcdcea7fe8586e3a3ccb3d9dc3b9408800d9814efb08e" +checksum = "b99c2b48934cd02a81032dd7428b7ae831a27794275bc94eba367418db8a9e55" dependencies = [ "arc-swap", - "arcstr", "async-trait", "bytes", "bytes-utils", - "cfg-if 1.0.0", "float-cmp", "futures", "lazy_static", @@ -1374,13 +1080,20 @@ dependencies = [ "rand", "redis-protocol", "semver", - "sha-1", + "socket2", "tokio", "tokio-stream", "tokio-util", "url", + "urlencoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -1440,34 +1153,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" -dependencies = [ - "fastrand 2.0.1", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.30" @@ -1476,7 +1161,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -1525,16 +1210,18 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "ghash" -version = "0.5.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", "polyval", @@ -1546,29 +1233,11 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" -version = "0.3.24" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" dependencies = [ "bytes", "fnv", @@ -1583,13 +1252,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -1599,31 +1277,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown", -] - -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", + "hashbrown 0.14.3", ] [[package]] @@ -1637,9 +1291,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1662,7 +1316,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac", "digest 0.9.0", ] @@ -1686,9 +1340,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1697,20 +1351,32 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", + "futures-core", "http", + "http-body", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" [[package]] name = "httparse" @@ -1721,27 +1387,17 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.28" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", @@ -1750,11 +1406,24 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", "tokio", - "tower-service", - "tracing", - "want", ] [[package]] @@ -1781,14 +1450,10 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.4.0" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" @@ -1800,26 +1465,14 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] @@ -1830,36 +1483,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "syn 2.0.52", ] [[package]] @@ -1877,33 +1501,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" -[[package]] -name = "jobserver" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1913,28 +1519,12 @@ dependencies = [ "spin 0.5.2", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if 1.0.0", - "windows-sys 0.48.0", -] - [[package]] name = "libm" version = "0.2.8" @@ -1952,12 +1542,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1972,16 +1556,14 @@ checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", + "serde", ] [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -dependencies = [ - "value-bag", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchers" @@ -2004,7 +1586,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "digest 0.10.7", ] @@ -2018,7 +1600,6 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" name = "migration" version = "0.1.0" dependencies = [ - "async-std", "sea-orm-migration", ] @@ -2055,9 +1636,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -2084,12 +1665,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.4.2", - "cfg-if 1.0.0", + "cfg-if", + "cfg_aliases", "libc", ] @@ -2119,6 +1701,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2211,9 +1804,9 @@ dependencies = [ [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" @@ -2222,7 +1815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -2238,7 +1831,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2249,9 +1842,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.100" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae94056a791d0e1217d18b6cbdccb02c61e3054fc69893607f4067e3bb0b1fd1" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2289,7 +1882,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2298,12 +1891,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" version = "0.12.1" @@ -2320,13 +1907,25 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.48.5", ] +[[package]] +name = "password-auth" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2a4764cc1f8d961d802af27193c6f4f0124bd0e76e8393cf818e18880f0524" +dependencies = [ + "argon2", + "getrandom", + "password-hash", + "rand_core", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -2361,22 +1960,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2391,17 +1990,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" -dependencies = [ - "atomic-waker", - "fastrand 2.0.1", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -2430,43 +2018,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.5.0" +name = "poly1305" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cfg-if 1.0.0", - "concurrent-queue", - "pin-project-lite", - "rustix 0.38.31", - "tracing", - "windows-sys 0.52.0", + "cpufeatures 0.2.12", + "opaque-debug", + "universal-hash", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", + "cfg-if", + "cpufeatures 0.2.12", "opaque-debug", "universal-hash", ] @@ -2484,13 +2053,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "prettyplease" -version = "0.2.16" +name = "proc-macro-crate" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "proc-macro2", - "syn 2.0.50", + "toml_edit", ] [[package]] @@ -2527,14 +2095,23 @@ dependencies = [ ] [[package]] -name = "proctitle" -version = "0.1.1" +name = "ptr_meta" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924cd8a0de90723d63fed19c5035ea129913a0bc998b37686a67f1eaf6a2aab5" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ - "lazy_static", - "libc", - "winapi", + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -2546,6 +2123,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2617,7 +2200,7 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -2632,9 +2215,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2653,19 +2236,90 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", - "spin 0.5.2", + "spin 0.9.8", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rpassword" +version = "7.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.48.0", ] [[package]] @@ -2689,30 +2343,36 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "rtoolbox" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "rust_decimal" +version = "1.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "b39449a79f45e8da28c57c341891b69a183044b29518bb8f86dbac9df60bb7df" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] [[package]] -name = "rustix" -version = "0.37.27" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" @@ -2723,7 +2383,7 @@ dependencies = [ "bitflags 2.4.2", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -2764,75 +2424,60 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] name = "sea-orm" -version = "0.12.14" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6632f499b80cc6aaa781b302e4c9fae663e0e3dcf2640e9d80034d5b10731efe" +checksum = "97b1218f3b8f95076954bde2e2a33852f873a91e5bbd49402f8fc2bb755c0ed3" dependencies = [ "async-stream", "async-trait", + "bigdecimal", "chrono", "futures", - "log", - "ouroboros", - "sea-orm-macros", - "sea-query", - "sea-query-binder", - "serde", - "sqlx", - "strum", - "thiserror", - "tracing", - "url", -] - -[[package]] -name = "sea-orm-cli" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465ea2308d4716837e9af4a2cff8e14c28135867a580bb93e9e03d408a3a6afb" -dependencies = [ - "chrono", - "clap", - "dotenvy", - "glob", - "regex", - "sea-schema", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "serde_json", + "sqlx", + "strum", + "thiserror", + "time", "tracing", - "tracing-subscriber", "url", + "uuid", ] [[package]] name = "sea-orm-macros" -version = "0.12.14" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec13bfb4c4aef208f68dbea970dd40d13830c868aa8dcb4e106b956e6bb4f2fa" +checksum = "7be16d30795cc707c355d1c0ba704db085d5bd507a858509a0d784189b8fe31b" dependencies = [ "heck", "proc-macro2", "quote", "sea-bae", - "syn 2.0.50", + "syn 2.0.52", "unicode-ident", ] [[package]] name = "sea-orm-migration" -version = "0.12.14" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac734b6e5610c2764056cc8495fbc293cd1c8ebe084fdfb74c3b0cdaaff9bb92" +checksum = "e4daac7104c6e919c4ae2da226b1ccb5d9b8c67046d85a597e20dee815d20319" dependencies = [ "async-trait", - "clap", - "dotenvy", "futures", "sea-orm", - "sea-orm-cli", "sea-schema", "tracing", "tracing-subscriber", @@ -2840,26 +2485,36 @@ dependencies = [ [[package]] name = "sea-query" -version = "0.30.7" +version = "0.31.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4166a1e072292d46dc91f31617c2a1cdaf55a8be4b5c9f4bf2ba248e3ac4999b" +checksum = "2a3d4c4e985f331e06266751243c5ebe6f61e961dda605d71ea2e2b016ef478b" dependencies = [ + "bigdecimal", "chrono", "derivative", "inherent", "ordered-float", + "rust_decimal", "sea-query-derive", + "serde_json", + "time", + "uuid", ] [[package]] name = "sea-query-binder" -version = "0.5.0" +version = "0.6.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9" +checksum = "ee39a8b40e6b4ee0510ee64c1079284559ee41f97919855b9cd7e43e1fb6ab04" dependencies = [ + "bigdecimal", "chrono", + "rust_decimal", "sea-query", + "serde_json", "sqlx", + "time", + "uuid", ] [[package]] @@ -2871,15 +2526,15 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", "thiserror", ] [[package]] name = "sea-schema" -version = "0.14.2" +version = "0.15.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d148608012d25222442d1ebbfafd1228dbc5221baf4ec35596494e27a2394e" +checksum = "4a03b47e6cf73c34d83fff934c169732530f6f3b0dd072725804961959b7d1df" dependencies = [ "futures", "sea-query", @@ -2899,13 +2554,10 @@ dependencies = [ ] [[package]] -name = "secrecy" -version = "0.8.0" +name = "seahash" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "zeroize", -] +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" @@ -2953,7 +2605,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -2967,16 +2619,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2990,14 +2632,12 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +name = "service" +version = "0.1.0" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", + "entity", + "sea-orm", + "tokio", ] [[package]] @@ -3006,8 +2646,8 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", + "cfg-if", + "cpufeatures 0.2.12", "digest 0.10.7", ] @@ -3018,8 +2658,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", + "cfg-if", + "cpufeatures 0.2.12", "digest 0.9.0", "opaque-debug", ] @@ -3030,25 +2670,25 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", + "cfg-if", + "cpufeatures 0.2.12", "digest 0.10.7", ] [[package]] name = "shadyurl-rust" -version = "0.1.0" +version = "0.2.0" dependencies = [ - "anyhow", - "argon2-kdf", "askama", "askama_axum", - "async-fred-session", + "async-trait", "axum", "axum-client-ip", "axum-login", - "axum_csrf", - "base64ct", + "axum-messages", + "base64 0.22.0", + "clap", + "csrf", "dotenvy", "entity", "envy", @@ -3056,20 +2696,22 @@ dependencies = [ "itertools", "lazy_static", "migration", - "nix", - "num_cpus", "once_cell", - "proctitle", + "password-auth", "rand", - "rand_chacha", "rand_distr", + "rpassword", "sea-orm", "sea-query", "serde", - "syslog-tracing", + "service", + "thiserror", + "time", "tokio", "tower", - "tower-http 0.4.4", + "tower-http", + "tower-sessions", + "tower-sessions-redis-store", "tracing", "tracing-subscriber", "url", @@ -3085,12 +2727,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3110,6 +2746,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "slab" version = "0.4.9" @@ -3127,22 +2769,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3200,8 +2832,9 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ - "ahash", + "ahash 0.8.11", "atoi", + "bigdecimal", "byteorder", "bytes", "chrono", @@ -3209,7 +2842,7 @@ dependencies = [ "crossbeam-queue", "dotenvy", "either", - "event-listener 2.5.3", + "event-listener", "futures-channel", "futures-core", "futures-intrusive", @@ -3224,16 +2857,19 @@ dependencies = [ "once_cell", "paste", "percent-encoding", + "rust_decimal", "serde", "serde_json", "sha2 0.10.8", "smallvec", "sqlformat", "thiserror", + "time", "tokio", "tokio-stream", "tracing", "url", + "uuid", ] [[package]] @@ -3284,6 +2920,7 @@ checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" dependencies = [ "atoi", "base64 0.21.7", + "bigdecimal", "bitflags 2.4.2", "byteorder", "bytes", @@ -3308,6 +2945,7 @@ dependencies = [ "percent-encoding", "rand", "rsa", + "rust_decimal", "serde", "sha1", "sha2 0.10.8", @@ -3315,7 +2953,9 @@ dependencies = [ "sqlx-core", "stringprep", "thiserror", + "time", "tracing", + "uuid", "whoami", ] @@ -3327,6 +2967,7 @@ checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", "base64 0.21.7", + "bigdecimal", "bitflags 2.4.2", "byteorder", "chrono", @@ -3345,8 +2986,10 @@ dependencies = [ "log", "md-5", "memchr", + "num-bigint", "once_cell", "rand", + "rust_decimal", "serde", "serde_json", "sha1", @@ -3355,7 +2998,9 @@ dependencies = [ "sqlx-core", "stringprep", "thiserror", + "time", "tracing", + "uuid", "whoami", ] @@ -3378,9 +3023,11 @@ dependencies = [ "percent-encoding", "serde", "sqlx-core", + "time", "tracing", "url", "urlencoding", + "uuid", ] [[package]] @@ -3400,6 +3047,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.0" @@ -3431,15 +3084,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -3447,25 +3112,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "syslog-tracing" -version = "0.3.0" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340b1540dcdb6b066bc2966e7974f977ab1a38f21b2be189014ffb0cc2405768" -dependencies = [ - "libc", - "tracing-core", - "tracing-subscriber", -] +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "cfg-if 1.0.0", - "fastrand 2.0.1", - "rustix 0.38.31", + "cfg-if", + "fastrand", + "rustix", "windows-sys 0.52.0", ] @@ -3486,7 +3146,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3495,7 +3155,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] @@ -3561,7 +3221,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -3575,7 +3235,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3603,6 +3263,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -3620,19 +3297,17 @@ dependencies = [ ] [[package]] -name = "tower-http" -version = "0.3.5" +name = "tower-cookies" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +checksum = "4fd0118512cf0b3768f7fcccf0bef1ae41d68f2b45edc1e77432b36c97c56c6d" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "futures-core", + "async-trait", + "axum-core", + "cookie", "futures-util", "http", - "http-body", - "http-range-header", + "parking_lot", "pin-project-lite", "tower-layer", "tower-service", @@ -3640,16 +3315,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.4.2", "bytes", - "futures-core", "futures-util", "http", "http-body", + "http-body-util", "http-range-header", "httpdate", "mime", @@ -3658,7 +3333,6 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", "tower-layer", "tower-service", "tracing", @@ -3676,6 +3350,71 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tower-sessions" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989b4d77286a7fb96b094b9e62c4524cebe7bb720769b41588ebaac5d9a787ca" +dependencies = [ + "async-trait", + "http", + "time", + "tokio", + "tower-cookies", + "tower-layer", + "tower-service", + "tower-sessions-core", + "tower-sessions-memory-store", + "tracing", +] + +[[package]] +name = "tower-sessions-core" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a9049748900860b01f92d3decf5fec71b9d75008f07732956288b003a2282f" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.22.0", + "futures", + "http", + "parking_lot", + "rand", + "serde", + "serde_json", + "thiserror", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "tower-sessions-memory-store" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36baf499920bb861ec9fa929a1f879cec0759af987c6360bd65226bc484ab846" +dependencies = [ + "async-trait", + "time", + "tokio", + "tower-sessions-core", +] + +[[package]] +name = "tower-sessions-redis-store" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c746ddeb702e9a933a1a61d1061d981c8788e13168137f9900dcc664dfec2f" +dependencies = [ + "async-trait", + "fred", + "rmp-serde", + "thiserror", + "time", + "tower-sessions-core", +] + [[package]] name = "tracing" version = "0.1.40" @@ -3696,7 +3435,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] @@ -3740,12 +3479,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typenum" version = "1.17.0" @@ -3788,6 +3521,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -3796,19 +3535,19 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "crypto-common", + "generic-array", "subtle", ] [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -3817,7 +3556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", ] @@ -3833,14 +3572,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "serde", +] + [[package]] name = "validator" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +checksum = "da339118f018cc70ebf01fafc103360528aad53717e4bf311db929cb01cb9345" dependencies = [ - "idna 0.4.0", - "lazy_static", + "idna", + "once_cell", "regex", "serde", "serde_derive", @@ -3851,28 +3599,16 @@ dependencies = [ [[package]] name = "validator_derive" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +checksum = "76e88ea23b8f5e59230bff8a2f03c0ee0054a61d5b8343a38946bcd406fe624c" dependencies = [ - "if_chain", - "lazy_static", + "darling", "proc-macro-error", "proc-macro2", "quote", "regex", - "syn 1.0.109", - "validator_types", -] - -[[package]] -name = "validator_types" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" -dependencies = [ - "proc-macro2", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -3881,12 +3617,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3899,69 +3629,48 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3969,51 +3678,33 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" - -[[package]] -name = "web-sys" -version = "0.3.68" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "which" -version = "4.4.2" +name = "whoami" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.31", + "redox_syscall", + "wasite", ] -[[package]] -name = "whoami" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" - [[package]] name = "winapi" version = "0.3.9" @@ -4042,7 +3733,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4060,7 +3751,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4080,17 +3771,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -4101,9 +3792,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -4113,9 +3804,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -4125,9 +3816,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -4137,9 +3828,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -4149,9 +3840,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -4161,9 +3852,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -4173,9 +3864,27 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] [[package]] name = "zerocopy" @@ -4194,7 +3903,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.52", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f919286..985f166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,12 @@ [package] name = "shadyurl-rust" -version = "0.1.0" +version = "0.2.0" edition = "2021" -rust-version = "1.65.0" +rust-version = "1.74.1" authors = ["Elizabeth Myers"] +categories = ["web-programming::http-server"] +keywords = ["web", "service", "axum", "http"] description = "A webapp that makes URLs shady" license = "CC0-1.0" homepage = "https://github.com/Elizafox/ShadyURL-Rust" @@ -24,7 +26,7 @@ repository = "https://github.com/Elizafox/ShadyURL-Rust" readme = "README.md" [workspace] -members = [".", "entity", "migration"] +members = [".", "entity", "migration", "service"] [profile.release] codegen-units = 1 @@ -37,47 +39,50 @@ default = ["sqlite"] mysql = ["sea-orm/sqlx-mysql", "sea-query/backend-postgres", "migration/mysql"] postgres = ["sea-orm/sqlx-postgres", "sea-query/backend-postgres", "migration/postgres"] sqlite = ["sea-orm/sqlx-sqlite", "sea-query/backend-sqlite", "migration/sqlite"] -with-tracing = ["axum/tracing", "tokio/tracing", "tower/tracing", "tower-http/tracing"] +tracing = ["tokio/tracing", "tower/tracing", "tower-http/tracing"] [dependencies] entity = { path = "entity" } migration = { path = "migration" } +service = { path = "service" } ###################################### # Package dependencies go below here # ###################################### -anyhow = { version = "1.0.80", features = ["backtrace"] } -argon2-kdf = "1.5.2" -askama = { version = "0.12.1", features = ["with-axum"] } -askama_axum = "0.3.0" -async-fred-session = "0.1.5" -axum = { version = "0.6.20", features = ["macros", "http2", "headers"] } -axum-client-ip = "0.4.2" -axum_csrf = { version = "0.7.2", features = ["layer"] } -axum-login = "0.5.0" -base64ct = { version = "1.6.0", features = ["std"] } +askama = { version = "0.12.1", features = ["with-axum", "urlencode", "mime", "mime_guess"], default-features = false } +askama_axum = { version = "0.4.0", default-features = false, features = ["urlencode"] } +async-trait = "0.1.77" +axum = { version = "0.7.4", default-features = false, features = ["form", "http1", "http2", "macros", "tokio", "tower-log"] } +axum-client-ip = "0.5.1" +axum-login = "0.14.0" +axum-messages = "0.5.0" +base64 = "0.22.0" +clap = { version = "4.5.2", features = ["cargo", "derive", "unicode"] } +csrf = "0.4.1" dotenvy = "0.15.7" envy = "0.4.2" heck = { version = "0.4.1", features = ["unicode"] } itertools = "0.12.1" lazy_static = "1.4.0" -nix = { version = "0.27.1", default-features = false, features = ["dir", "process", "user"] } -num_cpus = "1.16.0" once_cell = { version = "1.19.0", features = ["parking_lot"] } -proctitle = "0.1.1" -rand = { version = "0.8.5", features = ["min_const_gen"] } -rand_chacha = { version = "0.3.1", features = ["simd"] } +password-auth = "1.0.0" +#proctitle = "0.1.1" +rand="0.8.5" rand_distr = "0.4.3" -sea-orm = { version = "0.12.14", default-features = false, features = ["macros", "runtime-tokio-native-tls", "with-chrono"] } -sea-query = { version = "0.30.7", default-features = false, features = ["thread-safe", "with-chrono"] } +rpassword = "7.3.1" +sea-orm = { version = "1.0.0-rc.1", default-features = false, features = ["macros", "runtime-tokio-native-tls", "with-time"] } +sea-query = { version = "0.31.0-rc.4", default-features = false, features = ["thread-safe", "with-time"] } serde = { version = "1.0.197", features = ["derive"] } -syslog-tracing = "0.3.0" -tokio = { version = "1.36.0", features = ["fs", "macros", "parking_lot", "rt-multi-thread", "signal", "time"] } -tracing = { version = "0.1.40", features = ["async-await", "log"]} -tracing-subscriber = { version = "0.3.18", features = ["local-time", "parking_lot"]} +thiserror = "1.0.57" +time = "0.3.34" +tokio = { version = "1.36.0", default-features = false, features = ["macros", "parking_lot", "rt-multi-thread", "signal", "time"] } +tracing = { version = "0.1.40", features = ["async-await", "log"] } +tracing-subscriber = { version = "0.3.18", features = ["local-time", "parking_lot", "time"] } tower = { version = "0.4.13", features = ["timeout", "tokio"] } -tower-http = { version = "0.4.4", features = ["fs", "timeout", "tokio", "tower"] } +tower-http = { version = "0.5.2", features = ["fs", "normalize-path", "timeout", "tokio"] } +tower-sessions = { version = "0.11.0", default-features = false, features = ["axum-core"] } +tower-sessions-redis-store = "0.11.0" url = "2.5.0" -validator = { version = "0.16.1", features = ["derive"] } +validator = { version = "0.17.0", features = ["derive"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ed309fd --- /dev/null +++ b/build.rs @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * build.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +fn main() { + println!("cargo:rerun-if-changed=templates"); +} diff --git a/entity/Cargo.toml b/entity/Cargo.toml index 99dae92..fefc0af 100644 --- a/entity/Cargo.toml +++ b/entity/Cargo.toml @@ -15,6 +15,7 @@ name = "entity" version = "0.1.0" edition = "2021" +license = "CC0-1.0" publish = false [lib] @@ -22,4 +23,4 @@ name = "entity" path = "src/lib.rs" [dependencies] -sea-orm = { version = "^0", default-features = false, features = ["with-chrono"] } +sea-orm = { version = "1.0.0-rc.1", default-features = false, features = ["with-time"] } diff --git a/entity/src/entities/mod.rs b/entity/src/entities/mod.rs index 89b1f5c..56badff 100644 --- a/entity/src/entities/mod.rs +++ b/entity/src/entities/mod.rs @@ -17,3 +17,4 @@ pub mod prelude; pub mod url; +pub mod user; diff --git a/entity/src/entities/prelude.rs b/entity/src/entities/prelude.rs index 3ab193b..8730594 100644 --- a/entity/src/entities/prelude.rs +++ b/entity/src/entities/prelude.rs @@ -15,3 +15,4 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 pub use super::url::Entity as Url; +pub use super::user::Entity as User; diff --git a/entity/src/entities/user.rs b/entity/src/entities/user.rs new file mode 100644 index 0000000..05fff79 --- /dev/null +++ b/entity/src/entities/user.rs @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * entity/src/entities/user.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub username: String, + pub password_hash: String, +} + +impl std::fmt::Debug for Model { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("User") + .field("id", &self.id) + .field("username", &self.username) + .field("password_hash", &"[redacted]") + .finish() + } +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/env_example b/env_example index 891a03f..2a1ca0f 100644 --- a/env_example +++ b/env_example @@ -58,31 +58,5 @@ DATABASE_URL="sqlite:/tmp/test.db" # Redis URL, must start with redis:// as below. REDIS_URL="redis://127.0.0.1" -# This is for cookies, don't disclose it, and you really need to change this. -# This is encoded in base64 now; if you have a previous secret, you can do this: -# printf '%s' '' | base64 -# -# TIP: for new secrets, generate it with the script provided: -# /path/to/shadyurl-rust/utils/secret_key.sh -SECRET_KEY=ZHVtbXlkdW1teWR1bW15ZHVtbXlkdW1teWR1bW15ZHVtbXlkdW1teWR1bW15ZHVtbXlkdW1teWR1bW15ZHVtbXk= - -# Login username, for the /login endpoint for management -USERNAME=Elizafox - -# Password hash to use for the login endpoint; install argon2 and use the shell script provided: -# /path/to/shadyurl-rust/utils/password_hash.sh -PASSWORD_HASH='$argon2id$v=19$m=16,t=10,p=1$ZGFibW9GVU9GYVhQSkpxSw$6IJIHbZZzHyw/qVZK3EjZQ' - -# Whether or not to daemonise, set to true or false -# If you are running under a supervisor such as systemd, it's highly recommended to say "false" here. -DAEMON=false - -# Path to PID file, defaults to /var/run/shadyurl.pid -PID_FILE="/tmp/shadyurl.pid" - -# Set the log level, with 0 being only errors, and 4 being debug -# It's recommended to keep this around 0 or 1 -LOG_LEVEL=1 - -# Whether or not to log to stderr -LOG_STDERR=false +# Redis pool size, defaults to 3, increase if you need more +REDIS_POOL_SIZE=3 diff --git a/migration/Cargo.toml b/migration/Cargo.toml index b5fab43..f5cca99 100644 --- a/migration/Cargo.toml +++ b/migration/Cargo.toml @@ -15,6 +15,7 @@ name = "migration" version = "0.1.0" edition = "2021" +license = "CC0-1.0" publish = false [lib] @@ -28,5 +29,4 @@ postgres = ["sea-orm-migration/sqlx-postgres"] sqlite = ["sea-orm-migration/sqlx-sqlite"] [dependencies] -async-std = { version = "1", features = ["attributes", "tokio1"] } -sea-orm-migration = { version = "0.12.14", features = ["runtime-tokio-native-tls"] } +sea-orm-migration = { version = "1.0.0-rc.1", default-features = false, features = ["runtime-tokio-native-tls", "with-time"] } diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 60420ea..a2a4b9a 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -16,6 +16,7 @@ pub use sea_orm_migration::prelude::*; mod m20230702_081718_create_table; mod m20230713_214521_add_unique_constraint; +mod m20240302_082409_create_user_table; pub struct Migrator; @@ -25,6 +26,7 @@ impl MigratorTrait for Migrator { vec![ Box::new(m20230702_081718_create_table::Migration), Box::new(m20230713_214521_add_unique_constraint::Migration), + Box::new(m20240302_082409_create_user_table::Migration), ] } } diff --git a/migration/src/m20230713_214521_add_unique_constraint.rs b/migration/src/m20230713_214521_add_unique_constraint.rs index b0ef707..023e02f 100644 --- a/migration/src/m20230713_214521_add_unique_constraint.rs +++ b/migration/src/m20230713_214521_add_unique_constraint.rs @@ -1,3 +1,17 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * migration/src/m20230713_214521_add_unique_constraint.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + use sea_orm_migration::prelude::*; #[derive(Iden)] diff --git a/migration/src/m20240302_082409_create_user_table.rs b/migration/src/m20240302_082409_create_user_table.rs new file mode 100644 index 0000000..172a01e --- /dev/null +++ b/migration/src/m20240302_082409_create_user_table.rs @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * migration/src/m20240302_082409_create_user_table.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[allow(clippy::enum_variant_names)] +#[derive(Iden)] +enum User { + Table, + Id, + Username, + PasswordHash, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(User::Table) + .if_not_exists() + .col( + ColumnDef::new(User::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(User::Username) + .string() + .unique_key() + .not_null(), + ) + .col(ColumnDef::new(User::PasswordHash).string().not_null()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(User::Table).to_owned()) + .await + } +} diff --git a/service/Cargo.toml b/service/Cargo.toml new file mode 100644 index 0000000..130bfeb --- /dev/null +++ b/service/Cargo.toml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: CC0-1.0 +# +# service/Cargo.toml +# +# This file is a component of ShadyURL by Elizabeth Myers. +# +# To the extent possible under law, the person who associated CC0 with +# ShadyURL has waived all copyright and related or neighboring rights +# to ShadyURL. +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . + +[package] +name = "service" +version = "0.1.0" +edition = "2021" +license = "CC0-1.0" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "service" +path = "src/lib.rs" + +[features] +default = ["sqlite"] +mysql = ["sea-orm/sqlx-mysql"] +postgres = ["sea-orm/sqlx-postgres"] +sqlite = ["sea-orm/sqlx-sqlite"] + +[dependencies] +entity = { path = "../entity" } + +[dependencies.sea-orm] +version = "1.0.0-rc.1" +features = [ + "debug-print", + "runtime-tokio-native-tls", +] + +[dev-dependencies] +tokio = { version = "1.36", features = ["macros", "rt"] } diff --git a/service/src/lib.rs b/service/src/lib.rs new file mode 100644 index 0000000..d66d318 --- /dev/null +++ b/service/src/lib.rs @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * service/src/lib.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +mod mutation; +mod query; + +pub use mutation::*; +pub use query::*; + +pub use sea_orm; diff --git a/service/src/mutation.rs b/service/src/mutation.rs new file mode 100644 index 0000000..40c8780 --- /dev/null +++ b/service/src/mutation.rs @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * service/src/mutation.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use ::entity::{url, user}; +use sea_orm::*; + +use crate::Query; + +pub struct Mutation; + +impl Mutation { + pub async fn create_user( + db: &DbConn, + username: &str, + password_hash: &str, + ) -> Result { + user::ActiveModel { + username: Set(username.to_owned()), + password_hash: Set(password_hash.to_owned()), + ..Default::default() + } + .save(db) + .await + } + + pub async fn change_user_password( + db: &DbConn, + username: &str, + password_hash: &str, + ) -> Result { + let mut user: user::ActiveModel = Query::find_user_by_username(db, username) + .await? + .ok_or(DbErr::Custom("Cannot find user.".to_owned())) + .map(Into::into)?; + + user.password_hash = Set(password_hash.to_string()); + user.update(db).await.map(Into::into) + } + + pub async fn delete_user(db: &DbConn, id: i64) -> Result { + let user: user::ActiveModel = Query::find_user_by_id(db, id) + .await? + .ok_or(DbErr::Custom("Cannot find user.".to_owned())) + .map(Into::into)?; + + user.delete(db).await + } + + pub async fn create_url( + db: &DbConn, + url: &str, + shady: &str, + ip: Option, + ) -> Result { + url::ActiveModel { + url: Set(url.to_owned()), + shady: Set(shady.to_owned()), + ip: Set(ip), + ..Default::default() + } + .save(db) + .await + } + + pub async fn delete_url(db: &DbConn, id: i64) -> Result { + let url: url::ActiveModel = Query::find_url_by_id(db, id) + .await? + .ok_or(DbErr::Custom("Cannot find url.".to_owned())) + .map(Into::into)?; + + url.delete(db).await + } +} diff --git a/service/src/query.rs b/service/src/query.rs new file mode 100644 index 0000000..9e75188 --- /dev/null +++ b/service/src/query.rs @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * service/src/query.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use ::entity::{url, url::Entity as Url, user, user::Entity as User}; +use sea_orm::*; + +pub struct Query; + +impl Query { + pub async fn find_user_by_username( + db: &DbConn, + username: &str, + ) -> Result, DbErr> { + User::find() + .filter(user::Column::Username.eq(username)) + .one(db) + .await + } + + pub async fn find_user_by_id(db: &DbConn, id: i64) -> Result, DbErr> { + User::find_by_id(id).one(db).await + } + + pub async fn find_url_by_string(db: &DbConn, url: &str) -> Result, DbErr> { + Url::find().filter(url::Column::Url.eq(url)).all(db).await + } + + pub async fn find_url_by_shady_string( + db: &DbConn, + shady: &str, + ) -> Result, DbErr> { + Url::find() + .filter(url::Column::Shady.eq(shady)) + .one(db) + .await + } + + pub async fn find_url_by_id(db: &DbConn, id: i64) -> Result, DbErr> { + Url::find_by_id(id).one(db).await + } + + pub async fn fetch_all_urls(db: &DbConn) -> Result, DbErr> { + Url::find().order_by_asc(url::Column::Id).all(db).await + } +} diff --git a/src/auth.rs b/src/auth.rs index b6b6337..a6981db 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -14,28 +14,112 @@ use std::sync::Arc; -use argon2_kdf::Hash; -use axum_login::{ - extractors::AuthContext, memory_store::MemoryStore as AuthMemoryStore, secrecy::SecretVec, - AuthUser, RequireAuthorizationLayer, -}; +use async_trait::async_trait; +use axum_login::{AuthUser, AuthnBackend, UserId}; +use password_auth::verify_password; +use sea_orm::{DbConn, DbErr}; +use serde::Deserialize; +use tokio::task; + +use entity::user; +use service::Query; + +#[derive(Debug, thiserror::Error)] +#[allow(clippy::module_name_repetitions)] +pub enum AuthError { + #[error(transparent)] + DbErr(#[from] DbErr), + + #[error(transparent)] + TaskJoin(#[from] task::JoinError), +} + +// We marshall the database user in and out of this +#[derive(Clone)] +pub struct User(pub(crate) user::Model); + +impl std::fmt::Debug for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("User") + .field("id", &self.0.id) + .field("username", &self.0.username) + .field("password_hash", &"[redacted]") + .finish() + } +} + +// XXX - hacky but we can't implement AuthUser for a foreign type +// So we just fake it. +impl User { + pub(super) async fn find_user_by_username( + db: &DbConn, + username: &str, + ) -> Result, AuthError> { + let user = Query::find_user_by_username(db, username).await?; + + Ok(user.map(User)) + } + + pub(super) async fn find_user_by_id(db: &DbConn, id: i64) -> Result, AuthError> { + let user = Query::find_user_by_id(db, id).await?; + + Ok(user.map(User)) + } +} + +impl AuthUser for User { + type Id = i64; + + fn id(&self) -> Self::Id { + self.0.id + } + + fn session_auth_hash(&self) -> &[u8] { + self.0.password_hash.as_bytes() + } +} #[derive(Debug, Clone)] -pub(crate) struct User { - pub(crate) id: usize, +pub struct Backend { + db: Arc, +} + +impl Backend { + pub(crate) fn new(db: Arc) -> Self { + Self { db } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct Credentials { pub(crate) username: String, - pub(crate) password_hash: Arc, + pub(crate) password: String, + pub(crate) authenticity_token: String, } -impl AuthUser for User { - fn get_id(&self) -> usize { - self.id +#[async_trait] +impl AuthnBackend for Backend { + type User = User; + type Credentials = Credentials; + type Error = AuthError; + + async fn authenticate( + &self, + creds: Self::Credentials, + ) -> Result, Self::Error> { + let user = Self::User::find_user_by_username(&self.db, &creds.username).await?; + + task::spawn_blocking(|| { + Ok(user.filter(|user| verify_password(creds.password, &user.0.password_hash).is_ok())) + }) + .await? } - fn get_password_hash(&self) -> SecretVec { - SecretVec::new(self.password_hash.as_bytes().into()) + async fn get_user(&self, id: &UserId) -> Result, Self::Error> { + let user = Self::User::find_user_by_id(&self.db, *id).await?; + Ok(user) } } -pub(crate) type Auth = AuthContext>; -pub(crate) type RequireAuth = RequireAuthorizationLayer; +#[allow(clippy::module_name_repetitions)] +pub type AuthSession = axum_login::AuthSession; diff --git a/src/controllers/admin.rs b/src/controllers/admin.rs deleted file mode 100644 index 926f3a5..0000000 --- a/src/controllers/admin.rs +++ /dev/null @@ -1,253 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/controllers/admin.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::sync::Arc; - -use axum::{ - extract::State, - response::{IntoResponse, Redirect, Result}, - Extension, Form, -}; -use axum_client_ip::SecureClientIp; -use axum_csrf::CsrfToken; -use axum_login::axum_sessions::extractors::WritableSession; -use lazy_static::lazy_static; -use sea_orm::EntityTrait; -use serde::Deserialize; -use tokio::{sync::Semaphore, task::spawn_blocking}; -use tracing::{error, warn}; - -use crate::{ - auth::{Auth, User}, - err::{respond_internal_server_error, respond_not_authorised}, - templates::{AdminTemplate, LoginTemplate}, - AppState, -}; - -use entity::prelude::*; - -lazy_static! { - static ref PASSWORD_HASH_SEMAPHORE: Arc = Arc::new(Semaphore::new(num_cpus::get())); - static ref TOKEN_SEMAPHORE: Arc = Arc::new(Semaphore::new(num_cpus::get() * 2)); -} - -#[derive(Deserialize)] -pub(crate) struct LoginPayload { - username: String, - password: String, - auth_token: String, -} - -#[derive(Deserialize)] -pub(crate) struct DeletePayload { - id: i64, - auth_token: String, -} - -pub(crate) async fn login_page_handler( - token: CsrfToken, - mut session: WritableSession, - auth: Auth, - State(state): State, -) -> Result { - if auth.current_user.is_some() { - return Ok(Redirect::to("/admin").into_response()); - } - - // Shove the auth token calculation into a thread - // This is a CPU intensive operation and we don't wanna block everything. - // Why the double map_err? we get a Result>. - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let auth_token = spawn_blocking(move || { - let result = t.authenticity_token(); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - session - .insert("auth_token", auth_token.clone()) - .map_err(|_| respond_not_authorised(&state))?; - - let err_str = session.get::("login_error").unwrap_or_default(); - - // Remove stale login error - session.remove("login_error"); - - let t = LoginTemplate { - base_host: &state.base_host, - err_str: &err_str, - sitename: &state.sitename, - auth_token: &auth_token, - }; - Ok((token, t).into_response()) -} - -pub(crate) async fn login_handler( - token: CsrfToken, - mut session: WritableSession, - mut auth: Auth, - SecureClientIp(addr): SecureClientIp, - State(state): State, - Form(payload): Form, -) -> Result { - // Shove the auth token calculation into a thread - // This is a CPU intensive operation and we don't wanna block everything. - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let pt = payload.auth_token.clone(); - spawn_blocking(move || { - let result = t.verify(&pt); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - let auth_token = session - .get::("auth_token") - .ok_or(respond_not_authorised(&state))?; - - // Trash previous auth token after looking - session.remove("auth_token"); - - // Same as above - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let pt = auth_token.clone(); - spawn_blocking(move || { - let result = t.verify(&pt); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - if payload.username != state.user.username { - let ip = addr.to_string(); - warn!( - "Login attempt from {ip} (username {}): No such username", - &payload.username - ); - // Save the error and redirect - session - .insert("login_error", "Invalid username".to_string()) - .map_err(|_| respond_not_authorised(&state))?; - return Ok(Redirect::to("/login").into_response()); - } - - let permit = PASSWORD_HASH_SEMAPHORE - .clone() - .acquire_owned() - .await - .unwrap(); - let password_hash = Arc::clone(&state.user.password_hash); - let hash_verify = spawn_blocking(move || { - let result = password_hash.verify(payload.password.as_bytes()); - drop(permit); - result - }) - .await - .map_err(|_| respond_not_authorised(&state))?; - - if !hash_verify { - let ip = addr.to_string(); - warn!( - "Login attempt from {ip} (username {}): Invalid password", - &payload.username - ); - // Save the error and redirect - session - .insert("login_error", "Invalid password".to_string()) - .map_err(|_| respond_not_authorised(&state))?; - return Ok(Redirect::to("/login").into_response()); - } - - // Don't hold onto the session, login needs it, it will spin forever otherwise. - drop(session); - - auth.login(&state.user).await.unwrap(); - Ok((token, Redirect::to("/admin").into_response()).into_response()) -} - -pub(crate) async fn logout_handler(mut auth: Auth) -> Redirect { - auth.logout().await; - Redirect::to("/") -} - -pub(crate) async fn admin_handler( - token: CsrfToken, - mut session: WritableSession, - State(state): State, - Extension(_user): Extension, -) -> Result { - let results = Url::find() - .all(&state.db) - .await - .map_err(|_| respond_internal_server_error(&state))?; - - let auth_token = token - .authenticity_token() - .map_err(|_| respond_not_authorised(&state))?; - session - .insert("auth_token", auth_token.clone()) - .map_err(|_| respond_not_authorised(&state))?; - - let t = AdminTemplate { - base_host: &state.base_host, - sitename: &state.sitename, - urls: results, - auth_token: &auth_token, - }; - Ok((token, t).into_response()) -} - -pub(crate) async fn delete_handler( - token: CsrfToken, - mut session: WritableSession, - State(state): State, - Extension(_user): Extension, - Form(payload): Form, -) -> Result { - token - .verify(&payload.auth_token) - .map_err(|_| respond_not_authorised(&state))?; - - let auth_token = session - .get::("auth_token") - .ok_or(respond_not_authorised(&state))?; - - session.remove("auth_token"); - - token - .verify(&auth_token) - .map_err(|_| respond_not_authorised(&state))?; - - let _ = Url::delete_by_id(payload.id).exec(&state.db).await; - Ok(Redirect::to("/admin").into_response()) -} diff --git a/src/controllers/err.rs b/src/controllers/err.rs deleted file mode 100644 index 56f4cb1..0000000 --- a/src/controllers/err.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/controllers/err.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use axum::{ - extract::State, - http::{header::CONTENT_LENGTH, Request, StatusCode}, - middleware::Next, - response::{IntoResponse, Response}, - BoxError, -}; -use itertools::Itertools; -use tower::timeout::error::Elapsed; - -use crate::{state::AppState, templates::ErrorTemplate}; - -// This transforms errors without a body into errors that have one. -// This actually runs as a service, but shrug -pub(crate) async fn transform_error( - State(state): State, - request: Request, - next: Next, -) -> Response { - let response = next.run(request).await; - - let status_num = response.status().as_u16(); - if status_num < 400 { - // We only care about client or server errors - return response; - } - - let (parts, body) = response.into_parts(); - - // Sniff the content-length and see if we have any data - if let Some(content_length) = parts.headers.get(CONTENT_LENGTH) { - if let Ok(len) = std::str::from_utf8(content_length.as_bytes()) - .unwrap_or("0") - .parse::() - { - if len > 0 { - return Response::from_parts(parts, body); - } - } - } - - let status_string = parts.status.to_string(); - let (error_code, reason) = status_string.splitn(2, ' ').collect_tuple().map_or( - (format!("{:03}", status_num), status_string.clone()), - |(x, y)| (x.to_owned(), y.to_owned()), - ); - - let t = ErrorTemplate { - base_host: &state.base_host, - error_code: &error_code, - reason: &reason, - }; - (parts.status, t).into_response() -} - -pub(crate) async fn handle_timeout_error(err: BoxError) -> impl IntoResponse { - // Let transform_error handle it - let error_code = if err.is::() { - StatusCode::REQUEST_TIMEOUT - } else { - StatusCode::INTERNAL_SERVER_ERROR - }; - - error_code.into_response() -} diff --git a/src/controllers/shadify.rs b/src/controllers/shadify.rs deleted file mode 100644 index 22df5b7..0000000 --- a/src/controllers/shadify.rs +++ /dev/null @@ -1,205 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/controllers/shadify.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::sync::Arc; - -use axum::{ - extract::{Path, State}, - http::StatusCode, - response::{IntoResponse, Redirect, Result}, - Form, -}; -use axum_client_ip::SecureClientIp; -use axum_csrf::CsrfToken; -use axum_login::axum_sessions::extractors::WritableSession; -use lazy_static::lazy_static; -use sea_orm::{ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter}; -use serde::Deserialize; -use tokio::{sync::Semaphore, task::spawn_blocking}; -use tracing::{debug, error}; -use validator::Validate; - -use crate::{ - err::{respond_internal_server_error, respond_not_authorised, respond_not_found}, - generate::shady_filename, - templates::{IndexTemplate, PostErrorTemplate, PostTemplate}, - util::rng::default_rng, - validators::validate_url, - AppState, -}; - -use entity::{prelude::*, url as url_db}; - -lazy_static! { - static ref SHADY_FILE_SEMAPHORE: Arc = Arc::new(Semaphore::new(num_cpus::get())); - static ref TOKEN_SEMAPHORE: Arc = Arc::new(Semaphore::new(num_cpus::get() * 2)); -} - -#[derive(Deserialize, Validate)] -pub(crate) struct UrlPayload { - #[validate(custom = "validate_url")] - url: String, - auth_token: String, -} - -pub(crate) async fn root( - token: CsrfToken, - mut session: WritableSession, - State(state): State, -) -> Result { - // Shove the auth token calculation into a thread - // This is a CPU intensive operation and we don't wanna block everything. - // Why the double map_err? we get a Result>. - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let auth_token = spawn_blocking(move || { - let result = t.authenticity_token(); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - session - .insert("auth_token", auth_token.clone()) - .map_err(|_| respond_not_authorised(&state))?; - - let t = IndexTemplate { - base_host: &state.base_host, - sitename: &state.sitename, - auth_token: &auth_token, - }; - - Ok((token, t).into_response()) -} - -pub(crate) async fn accept_form( - token: CsrfToken, - mut session: WritableSession, - SecureClientIp(addr): SecureClientIp, - State(state): State, - Form(payload): Form, -) -> Result { - // Shove the auth token calculation into a thread - // This is a CPU intensive operation and we don't wanna block everything. - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let pt = payload.auth_token.clone(); - spawn_blocking(move || { - let result = t.verify(&pt); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - let auth_token = session - .get::("auth_token") - .ok_or(respond_not_authorised(&state))?; - - // Trash auth token - session.remove("auth_token"); - - // Same as above - let permit = TOKEN_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let t = token.clone(); - let pt = auth_token.clone(); - spawn_blocking(move || { - let result = t.verify(&pt); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Error spawning thread: {e}"); - respond_internal_server_error(&state) - })? - .map_err(|_| respond_not_authorised(&state))?; - - let url = &payload.url; - - if let Err(e) = payload.validate() { - let reason = e - .field_errors() - .get("url") - .map_or("Unknown error".to_string(), |v| v[0].code.to_string()); - debug!("User attempted to put in invalid URL: \"{url}\", reason: {reason}"); - let t = PostErrorTemplate { - base_host: &state.base_host, - url, - reason: &reason, - }; - return Ok((StatusCode::UNPROCESSABLE_ENTITY, t).into_response()); - } - - let permit = SHADY_FILE_SEMAPHORE.clone().acquire_owned().await.unwrap(); - let shady = spawn_blocking(move || { - let mut rng = default_rng(); - let result = shady_filename(&mut rng); - drop(permit); - result - }) - .await - .map_err(|e| { - error!("Failed to generate shady filename: {e}"); - respond_internal_server_error(&state) - })?; - - let ip = addr.to_string(); - - let url_db_obj = url_db::ActiveModel { - url: Set(url.to_string()), - shady: Set(shady.to_string()), - ip: Set(Some(ip)), - ..Default::default() - }; - url_db_obj.insert(&state.db).await.map_err(|e| { - error!("Failed to add object to database: {e}"); - respond_internal_server_error(&state) - })?; - - let t = PostTemplate { - base_host: &state.base_host, - shady_host: &state.shady_host, - url, - shady: &shady, - }; - Ok((StatusCode::OK, t).into_response()) -} - -pub(crate) async fn get_shady( - State(state): State, - Path(shady): Path, -) -> Result { - let result = Url::find() - .filter(url_db::Column::Shady.eq(shady)) - .one(&state.db) - .await - .map_err(|e| { - error!("Failed to search database: {e}"); - respond_internal_server_error(&state) - })?; - Ok(match result { - Some(row) => Redirect::to(&row.url).into_response(), - None => respond_not_found(&state), - }) -} diff --git a/src/csrf.rs b/src/csrf.rs new file mode 100644 index 0000000..561e618 --- /dev/null +++ b/src/csrf.rs @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/csrf.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use base64::{prelude::*, DecodeError}; +use csrf::{ChaCha20Poly1305CsrfProtection, CsrfError, CsrfProtection}; +use tower_sessions::Session; + +#[derive(Debug, thiserror::Error)] +pub enum VerifyCsrfError { + #[error(transparent)] + Decode(#[from] DecodeError), + + #[error(transparent)] + Csrf(#[from] CsrfError), + + #[error("Could not validate CSRF token: {}", .0)] + CsrfValidation(String), +} + +pub async fn verify( + session: &Session, + form_token: &str, + protect: &ChaCha20Poly1305CsrfProtection, +) -> Result<(), VerifyCsrfError> { + let authenticity_token: String = session + .remove("authenticity_token") + .await + .unwrap_or_default() + .unwrap_or_default(); + + let token_bytes = BASE64_STANDARD.decode(form_token.as_bytes())?; + let session_bytes = BASE64_STANDARD.decode(authenticity_token.as_bytes())?; + + let parsed_token = protect.parse_token(&token_bytes)?; + let parsed_session = protect.parse_cookie(&session_bytes)?; + + if !protect.verify_token_pair(&parsed_token, &parsed_session) { + return Err(VerifyCsrfError::CsrfValidation( + "CSRF tokens could not be verified".to_string(), + )); + } + + Ok(()) +} diff --git a/src/daemon.rs b/src/daemon.rs deleted file mode 100644 index f074ed0..0000000 --- a/src/daemon.rs +++ /dev/null @@ -1,150 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/daemon.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::ffi::{CStr, CString}; -use std::fs::File; -use std::io::prelude::*; -use std::os::fd::AsRawFd; -use std::process::exit; - -use anyhow::{anyhow, bail, Error, Result}; -use nix::fcntl::{flock, FlockArg}; -use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; -use nix::sys::stat::{umask, Mode}; -use nix::unistd::*; - -use crate::loadenv::EnvVars; - -// This bad hack is because nix erroneously doesn't define initgroups for macOS -#[cfg(target_os = "macos")] -#[inline] -fn do_initgroups(name: &CStr, gid: Gid) -> Result<()> { - use nix::libc; - use std::io; - // SAFETY: name is not mutated and should be a safe value, gid is a safe cast - if unsafe { libc::initgroups(name.as_ptr() as _, gid.as_raw() as _) } != 0 { - return Err(Error::new(io::Error::last_os_error()).context("initgroups() failed")); - } - - Ok(()) -} - -#[cfg(not(target_os = "macos"))] -#[inline] -fn do_initgroups(name: &CStr, gid: Gid) -> Result<()> { - initgroups(name, gid).map_err(|e| Error::new(e).context("initgroups() failed"))?; - Ok(()) -} - -#[inline] -pub(crate) fn close_stdio() -> Result<()> { - close(STDIN_FILENO)?; - - // As a result of the above this should now be stdin - let dev_null_file = Box::leak(Box::new( - File::options() - .write(true) - .open("/dev/null") - .map_err(|e| Error::new(e).context("Failed to open /dev/null"))?, - )); - - // Close the major file descriptors - if dev_null_file.as_raw_fd() != STDIN_FILENO { - bail!("Failed to reopen stdin"); - } - - if dup2(STDIN_FILENO, STDOUT_FILENO)? != STDOUT_FILENO { - bail!("Failed to close stdout"); - } - - if dup2(STDIN_FILENO, STDERR_FILENO)? != STDERR_FILENO { - bail!("Failed to close stderr"); - } - - Ok(()) -} - -#[inline] -pub(crate) fn open_pid_file(env: &EnvVars) -> Result { - // Open the PID file with current privileges before dropping - // We leak because we want this to live forever - let mut pid_file = File::options() - .create(true) - .read(true) - .write(true) - .open(&env.pid_file)?; - - // Lock exclusively - flock(pid_file.as_raw_fd(), FlockArg::LockExclusiveNonblock) - .map_err(|e| Error::new(e).context("PID file is locked (process already running?)"))?; - - // Erase and write new PID - ftruncate(&pid_file, 0)?; - pid_file - .write_all(getpid().to_string().as_bytes()) - .map_err(|e| Error::new(e).context("Failed to write to PID file"))?; - - Ok(pid_file) -} - -pub(crate) fn drop_privileges(env: &EnvVars) -> Result<()> { - if let Some(daemon_user) = &env.daemon_user { - let user = User::from_name(daemon_user)?.ok_or(anyhow!("No such user {daemon_user}"))?; - - let group = match &env.daemon_group { - Some(daemon_group) => { - Group::from_name(daemon_group)?.ok_or(anyhow!("No such group {daemon_group}"))? - } - None => Group::from_gid(user.gid)? - .ok_or(anyhow!("User {daemon_user} GID is nonexistent"))?, - }; - - // Drop privileges - let username_c = CString::new(user.name)?; - do_initgroups(username_c.as_c_str(), group.gid)?; - setgid(group.gid).map_err(|e| Error::new(e).context("setgid() failed"))?; - setuid(user.uid).map_err(|e| Error::new(e).context("setuid() failed"))?; - } else if let Some(daemon_group) = &env.daemon_group { - let group = - Group::from_name(daemon_group)?.ok_or(anyhow!("No such group {daemon_group}"))?; - setgid(group.gid).map_err(|e| Error::new(e).context("setgid() failed"))?; - } - - Ok(()) -} - -#[inline] -pub(crate) fn to_background() -> Result<()> { - // Perform double fork to ensure we can't get a TTY - // SAFETY: it's fork, what do you want? - match unsafe { fork().map_err(|e| Error::new(e).context("fork() failed"))? } { - ForkResult::Parent { .. } => exit(0), - ForkResult::Child => {} - } - - setsid()?; - - // Second fork, parent is session leader but dead, so we can never regain a TTY - // SAFETY: it's fork, what do you want? - match unsafe { fork().map_err(|e| Error::new(e).context("fork() failed"))? } { - ForkResult::Parent { .. } => exit(0), - ForkResult::Child => {} - } - Ok(()) -} - -#[inline] -pub(crate) fn set_umask() { - umask(Mode::from_bits(0o137).expect("Unexpected failure in getting mode bits")); -} diff --git a/src/database.rs b/src/database.rs deleted file mode 100644 index 2c05873..0000000 --- a/src/database.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/database.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use anyhow::{Error, Result}; -use migration::{Migrator, MigratorTrait}; -use sea_orm::{ConnectOptions, Database, DatabaseConnection}; -use tokio::time::Duration; - -use crate::loadenv::EnvVars; - -pub(crate) async fn get_db(env: &EnvVars) -> Result { - let mut opt = ConnectOptions::new(env.database_url.clone()); - opt.max_connections(100) - .min_connections(5) - .connect_timeout(Duration::from_secs(10)) - .acquire_timeout(Duration::from_secs(10)) - .idle_timeout(Duration::from_secs(10)) - .max_lifetime(Duration::from_secs(10)) - .sqlx_logging(false); - - let db = Database::connect(opt) - .await - .map_err(|e| Error::new(e).context("Could not open database"))?; - - Migrator::up(&db, None) - .await - .map_err(|e| Error::new(e).context("Could not migrate database"))?; - - Ok(db) -} diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 0000000..4147098 --- /dev/null +++ b/src/env.rs @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/env.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use axum_client_ip::SecureClientIpSource; +use base64::prelude::*; +use dotenvy::dotenv; +use envy::from_env; +use rand::{prelude::*, thread_rng}; +use serde::{ + de::{Deserializer, Error}, + Deserialize, +}; +use validator::Validate; + +mod defaults { + use super::{thread_rng, Rng}; + + pub(super) fn redis_url() -> String { + "redis://127.0.0.1".to_string() + } + + pub(super) const fn redis_pool_size() -> usize { + 2 + } + + pub(super) fn sitename() -> String { + "ShadyURL".to_string() + } + + pub(super) fn csrf_key() -> [u8; 32] { + let mut ret = [0u8; 32]; + thread_rng().fill(&mut ret[..]); + ret + } +} + +mod deserializers { + use super::{Deserialize, Deserializer, Engine, Error, BASE64_STANDARD}; + + pub(super) fn csrf_key<'de, D>(d: D) -> Result<[u8; 32], D::Error> + where + D: Deserializer<'de>, + { + let key_b64 = String::deserialize(d)?; + let val = BASE64_STANDARD + .decode(key_b64) + .map_err(|e| Error::custom(format!("Could not decode csrf key: {e}")))?; + + let ret: [u8; 32] = val.try_into().map_err(|v: Vec| { + Error::custom(format!( + "Could not decode csrf key: length was incorrect (expected 32 bytes, got {})", + v.len() + )) + })?; + + Ok(ret) + } +} + +#[derive(Clone, Validate, Deserialize)] +pub struct Vars { + #[validate(length(min = 4))] + pub(crate) base_host: String, + #[serde(default)] + pub(crate) shady_host: String, + #[serde(default = "defaults::sitename")] + pub(crate) sitename: String, + + #[validate(length(min = 1))] + pub(crate) bind: String, + + pub(crate) ip_source: SecureClientIpSource, + + #[validate(url)] + pub(crate) database_url: String, + #[serde(default = "defaults::redis_url")] + #[validate(url)] + pub(crate) redis_url: String, + #[serde(default = "defaults::redis_pool_size")] + #[validate(range(min = 1))] + pub(crate) redis_pool_size: usize, + + #[serde( + deserialize_with = "deserializers::csrf_key", + default = "defaults::csrf_key" + )] + pub(crate) csrf_key: [u8; 32], +} + +impl Vars { + pub(crate) fn load_env() -> Result> { + dotenv()?; + let mut env: Self = from_env()?; + if env.shady_host.is_empty() { + env.shady_host = env.base_host.clone(); + } + env.validate()?; + Ok(env) + } +} diff --git a/src/err.rs b/src/err.rs deleted file mode 100644 index cd1028a..0000000 --- a/src/err.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/err.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use axum::{ - http::StatusCode, - response::{IntoResponse, Response}, -}; - -use crate::{state::AppState, templates::ErrorTemplate}; - -pub(crate) fn respond_not_authorised(state: &AppState) -> Response { - let t = ErrorTemplate { - base_host: &state.base_host, - error_code: "403", - reason: "Not authorised", - }; - (StatusCode::FORBIDDEN, t).into_response() -} - -pub(crate) fn respond_not_found(state: &AppState) -> Response { - let t = ErrorTemplate { - base_host: &state.base_host, - error_code: "404", - reason: "File not found", - }; - (StatusCode::NOT_FOUND, t).into_response() -} - -pub(crate) fn respond_internal_server_error(state: &AppState) -> Response { - let t = ErrorTemplate { - base_host: &state.base_host, - error_code: "500", - reason: "Internal server error", - }; - (StatusCode::INTERNAL_SERVER_ERROR, t).into_response() -} diff --git a/src/error_response.rs b/src/error_response.rs new file mode 100644 index 0000000..d6aeb74 --- /dev/null +++ b/src/error_response.rs @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/error_response.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use askama_axum::Template; +use axum::{ + body::Body, + http::StatusCode, + response::{IntoResponse, Response}, +}; + +use crate::{ + auth::{AuthError, Backend}, + csrf::VerifyCsrfError, +}; + +#[derive(Debug, thiserror::Error)] +pub enum AppError { + #[error(transparent)] + Csrf(#[from] csrf::CsrfError), + + #[error(transparent)] + VerifyCsrf(#[from] VerifyCsrfError), + + #[error(transparent)] + Auth(#[from] AuthError), + + #[error(transparent)] + AxumLogin(#[from] axum_login::Error), + + #[error(transparent)] + Session(#[from] tower_sessions::session::Error), + + #[error(transparent)] + Db(#[from] sea_orm::DbErr), + + #[error("Could not validate URL {}: {}", .1, .0)] + UrlValidation(String, String), + + #[error("Not found")] + NotFound, + + #[error("Unauthorized")] + Unauthorized, +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + match self { + Self::VerifyCsrf(e) => ErrorResponse::bad_request(e.to_string().as_ref()), + Self::UrlValidation(error_reason, url) => { + ErrorResponse::url_submission(&error_reason, &url) + } + Self::NotFound => ErrorResponse::not_found(), + Self::Unauthorized => ErrorResponse::unauthorized(), + _ => ErrorResponse::internal_server_error(self.to_string().as_str()), + } + } +} + +#[derive(Template)] +#[template(path = "errors/code/400.html")] +struct BadRequestTemplate<'a> { + error_reason: &'a str, +} + +#[derive(Template)] +#[template(path = "errors/code/403.html")] +struct UnauthorizedTemplate; + +#[derive(Template)] +#[template(path = "errors/code/404.html")] +struct NotFoundTemplate; + +#[derive(Template)] +#[template(path = "errors/code/500.html")] +struct InternalServerErrorTemplate<'a> { + error_reason: &'a str, +} + +#[derive(Template)] +#[template(path = "errors/form/url.html")] +struct UrlSubmissionErrorTemplate<'a> { + error_reason: &'a str, + url: &'a str, +} + +pub struct ErrorResponse; + +impl ErrorResponse { + pub(crate) fn bad_request(error_reason: &str) -> Response { + let t = BadRequestTemplate { error_reason }; + (StatusCode::BAD_REQUEST, t).into_response() + } + + pub(crate) fn unauthorized() -> Response { + (StatusCode::UNAUTHORIZED, UnauthorizedTemplate).into_response() + } + + pub(crate) fn not_found() -> Response { + (StatusCode::NOT_FOUND, NotFoundTemplate).into_response() + } + + pub(crate) fn internal_server_error(error_reason: &str) -> Response { + let t = InternalServerErrorTemplate { error_reason }; + (StatusCode::INTERNAL_SERVER_ERROR, t).into_response() + } + + pub(crate) fn url_submission<'a>(error_reason: &'a str, url: &'a str) -> Response { + let t = UrlSubmissionErrorTemplate { error_reason, url }; + (StatusCode::UNPROCESSABLE_ENTITY, t).into_response() + } +} diff --git a/src/generate.rs b/src/generate.rs index e83b7ae..45a1572 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -14,10 +14,8 @@ use once_cell::sync::Lazy; use rand::{ - distributions::{DistString, Distribution, Uniform}, + distributions::{DistString, Uniform}, prelude::*, - seq::SliceRandom, - Rng, }; use rand_distr::Alphanumeric; @@ -33,1169 +31,1178 @@ enum Mangler { HeckTransform, } -fn generate_hash(rng: &mut dyn RngCore) -> String { - let distr_count = Lazy::new(|| Uniform::new_inclusive(5, 9)); - let count = distr_count.sample(rng); - Alphanumeric.sample_string(rng, count) -} +pub struct Generator; -fn perform_mangle(rng: &mut dyn RngCore, mangler: Mangler, fragment: &str) -> String { - match mangler { - Mangler::RandomUppercase => fragment - .chars() - .map(|ch| { - let distr_cap = Lazy::new(|| Uniform::new(0, 3)); - if (*distr_cap).sample(rng) == 0 { - ch.to_uppercase().collect() - } else { - ch.to_string() - } - }) - .collect(), - Mangler::AllUppercase => fragment.to_uppercase(), - Mangler::ReplaceSeps => fragment - .chars() - .map(|ch| { - let distr_replace = Lazy::new(|| Uniform::new(0, 4)); - if ch == '-' && (*distr_replace).sample(rng) == 0 { - arr!(const SEPS: [&str; _] = ["!", "_", "+", "$"]); - SEPS.choose(rng).unwrap().to_string() - } else { - ch.to_string() - } - }) - .collect(), - Mangler::NumberLookalike => fragment - .chars() - .map(|ch| { - let distr_replace = Lazy::new(|| Uniform::new(0, 4)); - if (*distr_replace).sample(rng) == 0 { - match ch { - 'o' | 'O' => '0', - 'a' | 'A' => '4', - 'e' | 'E' => '3', - 'g' | 'G' => '9', - 'i' | 'I' | 'l' | 'L' => '1', - 's' | 'S' => '5', - 't' | 'T' => '7', - _ => ch, +impl Generator { + fn generate_hash() -> String { + let mut rng = thread_rng(); + let distr_count = Lazy::new(|| Uniform::new_inclusive(5, 9)); + let count = distr_count.sample(&mut rng); + Alphanumeric.sample_string(&mut rng, count) + } + + fn perform_mangle(mangler: Mangler, fragment: &str) -> String { + let mut rng = thread_rng(); + match mangler { + Mangler::RandomUppercase => fragment + .chars() + .map(|ch| { + let distr_cap = Lazy::new(|| Uniform::new(0, 3)); + if (*distr_cap).sample(&mut rng) == 0 { + ch.to_uppercase().collect() + } else { + ch.to_string() } - } else { - ch + }) + .collect(), + Mangler::AllUppercase => fragment.to_uppercase(), + Mangler::ReplaceSeps => fragment + .chars() + .map(|ch| { + let distr_replace = Lazy::new(|| Uniform::new(0, 4)); + if ch == '-' && (*distr_replace).sample(&mut rng) == 0 { + arr!(const SEPS: [&str; _] = ["!", "_", "+", "$"]); + (*SEPS.choose(&mut rng).unwrap()).to_string() + } else { + ch.to_string() + } + }) + .collect(), + Mangler::NumberLookalike => fragment + .chars() + .map(|ch| { + let distr_replace = Lazy::new(|| Uniform::new(0, 4)); + if (*distr_replace).sample(&mut rng) == 0 { + match ch { + 'o' | 'O' => '0', + 'a' | 'A' => '4', + 'e' | 'E' => '3', + 'g' | 'G' => '9', + 'i' | 'I' | 'l' | 'L' => '1', + 's' | 'S' => '5', + 't' | 'T' => '7', + _ => ch, + } + } else { + ch + } + }) + .collect(), + Mangler::HeckTransform => { + let distr_transform = Lazy::new(|| Uniform::new(0, 6)); + match (*distr_transform).sample(&mut rng) { + 0 => heck::AsLowerCamelCase(fragment).to_string(), + 1 => heck::AsUpperCamelCase(fragment).to_string(), + 2 => heck::AsShoutyKebabCase(fragment).to_string(), + 3 => heck::AsShoutySnakeCase(fragment).to_string(), + 4 => heck::AsSnakeCase(fragment).to_string(), + 5 => heck::AsTrainCase(fragment).to_string(), + _ => unreachable!(), } - }) - .collect(), - Mangler::HeckTransform => { - let distr_transform = Lazy::new(|| Uniform::new(0, 6)); - match (*distr_transform).sample(rng) { - 0 => heck::AsLowerCamelCase(fragment).to_string(), - 1 => heck::AsUpperCamelCase(fragment).to_string(), - 2 => heck::AsShoutyKebabCase(fragment).to_string(), - 3 => heck::AsShoutySnakeCase(fragment).to_string(), - 4 => heck::AsSnakeCase(fragment).to_string(), - 5 => heck::AsTrainCase(fragment).to_string(), - _ => unreachable!(), } + Mangler::NoOp => fragment.to_string(), } - Mangler::NoOp => fragment.to_string(), } -} -fn get_mangler(rng: &mut dyn RngCore) -> Mangler { - let distr_mangle = Lazy::new(|| Uniform::new(0, 15)); - match (*distr_mangle).sample(rng) { - // 1/3 probability of selecting a mangler - 0 => Mangler::AllUppercase, - 1 => Mangler::RandomUppercase, - 2 => Mangler::ReplaceSeps, - 3 => Mangler::NumberLookalike, - 4 => Mangler::HeckTransform, - _ => Mangler::NoOp, + fn get_mangler() -> Mangler { + let mut rng = thread_rng(); + let distr_mangle = Lazy::new(|| Uniform::new(0, 15)); + match (*distr_mangle).sample(&mut rng) { + // 1/3 probability of selecting a mangler + 0 => Mangler::AllUppercase, + 1 => Mangler::RandomUppercase, + 2 => Mangler::ReplaceSeps, + 3 => Mangler::NumberLookalike, + 4 => Mangler::HeckTransform, + _ => Mangler::NoOp, + } } -} -fn mangle_fragment(rng: &mut dyn RngCore, fragment: &str) -> String { - // Select mangling function - let distr_second_mangler = Lazy::new(|| Uniform::new(0, 4)); - let mangler = get_mangler(rng); - let new = perform_mangle(rng, mangler, fragment); + fn mangle_fragment(fragment: &str) -> String { + // Select mangling function + let mut rng = thread_rng(); + let distr_second_mangler = Lazy::new(|| Uniform::new(0, 4)); + let mangler = Self::get_mangler(); + let new = Self::perform_mangle(mangler, fragment); - if (*distr_second_mangler).sample(rng) == 0 { - // 1/4 chance to apply a second mangler - let mangler = match mangler { - Mangler::AllUppercase | Mangler::RandomUppercase => { - // Don't repeat a case mangling - if rng.gen() { - Mangler::ReplaceSeps - } else { - Mangler::NumberLookalike - } - } - Mangler::ReplaceSeps => match rng.gen_range(0..3) { - 0 => Mangler::AllUppercase, - 1 => Mangler::RandomUppercase, - 2 => Mangler::NumberLookalike, - _ => unreachable!(), - }, - Mangler::NumberLookalike => match rng.gen_range(0..3) { - 0 => Mangler::AllUppercase, - 1 => Mangler::RandomUppercase, - 2 => Mangler::ReplaceSeps, - _ => unreachable!(), - }, - Mangler::HeckTransform => { - if rng.gen() { - Mangler::ReplaceSeps - } else { - Mangler::NumberLookalike + if (*distr_second_mangler).sample(&mut rng) == 0 { + // 1/4 chance to apply a second mangler + let mangler = match mangler { + Mangler::AllUppercase | Mangler::RandomUppercase | Mangler::HeckTransform => { + // Don't repeat a case mangling or heck transform + if rng.gen() { + Mangler::ReplaceSeps + } else { + Mangler::NumberLookalike + } } - } - Mangler::NoOp => Mangler::NoOp, - }; + Mangler::ReplaceSeps => match rng.gen_range(0..3) { + 0 => Mangler::AllUppercase, + 1 => Mangler::RandomUppercase, + 2 => Mangler::NumberLookalike, + _ => unreachable!(), + }, + Mangler::NumberLookalike => match rng.gen_range(0..3) { + 0 => Mangler::AllUppercase, + 1 => Mangler::RandomUppercase, + 2 => Mangler::ReplaceSeps, + _ => unreachable!(), + }, + Mangler::NoOp => Mangler::NoOp, + }; - return perform_mangle(rng, mangler, &new); + return Self::perform_mangle(mangler, &new); + } + + new } - new -} + pub(crate) fn shady_filename() -> String { + arr!(const SEPS: [&str; _] = ["!", "_", "+", "~"]); -pub(crate) fn shady_filename(rng: &mut dyn RngCore) -> String { - arr!(const SEPS: [&str; _] = ["!", "_", "+", "~"]); + let mut rng = thread_rng(); - // These never change, so no point in regenerating them each time - let distr_count = Lazy::new(|| Uniform::new_inclusive(3, 6)); + // These never change, so no point in regenerating them each time + let distr_count = Lazy::new(|| Uniform::new_inclusive(3, 6)); - let token_count = distr_count.sample(rng); - let mut nsfw_str_count = token_count; + let token_count = distr_count.sample(&mut rng); + let mut nsfw_str_count = token_count; - let hash = generate_hash(rng); - let hash_pos = rng.gen_range(1..token_count); + let hash = Self::generate_hash(); + let hash_pos = rng.gen_range(1..token_count); - let fake_extension_pos = if rng.gen() { - loop { - let n = rng.gen_range(1..token_count); - if n != hash_pos { - nsfw_str_count -= 1; - break n; + let fake_extension_pos = if rng.gen() { + loop { + let n = rng.gen_range(1..token_count); + if n != hash_pos { + nsfw_str_count -= 1; + break n; + } } - } - } else { - // Deliberately out of range, so it won't be generated. - token_count + 1 - }; + } else { + // Deliberately out of range, so it won't be generated. + token_count + 1 + }; - // Gather unique strings up front - let mut nsfw_strs: Vec<_> = NSFW - .choose_multiple(rng, nsfw_str_count) - .map(|s| mangle_fragment(rng, s)) - .collect(); + // Gather unique strings up front + let mut nsfw_strs: Vec<_> = self::strings::NSFW + .choose_multiple(&mut rng, nsfw_str_count) + .map(|s| Self::mangle_fragment(s)) + .collect(); - // nsfw strings + extension - let mut out = Vec::with_capacity(token_count); - for i in 0..token_count { - if i > 0 && i != fake_extension_pos { - // Prepend - // SAFETY: never fails - out.push(unsafe { SEPS.choose(rng).unwrap_unchecked().to_string() }); + // nsfw strings + extension + let mut out = Vec::with_capacity(token_count); + for i in 0..token_count { + if i > 0 && i != fake_extension_pos { + // Prepend + // SAFETY: never fails + out.push(unsafe { (*SEPS.choose(&mut rng).unwrap_unchecked()).to_string() }); + } + + let push_val = if i == hash_pos { + hash.clone() + } else if i == fake_extension_pos { + // SAFETY: never fails + unsafe { (*self::strings::EXT.choose(&mut rng).unwrap_unchecked()).to_string() } + } else { + // SAFETY: nsfw_strs always has enough strings + unsafe { nsfw_strs.pop().unwrap_unchecked() } + }; + out.push(push_val); } - let push_val = if i == hash_pos { - hash.clone() - } else if i == fake_extension_pos { + if rng.gen() || fake_extension_pos <= token_count { + // Add extension // SAFETY: never fails - unsafe { EXT.choose(rng).unwrap_unchecked().to_string() } - } else { - // SAFETY: nsfw_strs always has enough strings - unsafe { nsfw_strs.pop().unwrap_unchecked() } - }; - out.push(push_val); - } + out.push(unsafe { + (*self::strings::EXT_EXE.choose(&mut rng).unwrap_unchecked()).to_string() + }); + } - if rng.gen() || fake_extension_pos <= token_count { - // Add extension - // SAFETY: never fails - out.push(unsafe { EXT_EXE.choose(rng).unwrap_unchecked().to_string() }); + out.into_iter().collect() } - - out.into_iter().collect() } -arr!(const NSFW: [&str; _] = [ - "---click-here---", - "---install-virus---", - "0percentartificial", - "0percentrisk", - "1$-iphone", - "1$-android", - "100percentlegal", - "100percentnatural", - "20percentoff", - "300deadmen", - "30waystokillyourwife", - "4chanhacks", - "419scam", - "420", - "500$-cashprize", - "69", - "7-11-robbery", - "800-dollars-4u", - "9-11-jumper", - "9-11-video", - "911-call", - "abuse", - "adblock-bypass", - "admin", - "ads", - "advert", - "affair", - "aids-bugcatching-parties", - "al-qaeda", - "al-qaeda-beheading-vids", - "al-qaeda-chat", - "al-qaeda-messageboard", - "al-qaeda-signup", - "alien-abduction", - "all-natural", - "ambien", - "anal", - "anal-penetration", - "analsex", - "ancient-cure", - "ancient-diet-pills", - "android-unlock", - "anti-aging-pills", - "anti-e-pills", - "anti-estrogen-pills", - "anti-t-pills", - "anti-testosterone-pills", - "antifa-kills-maga", - "antifamurder", - "antivirus", - "apple-giveaway", - "aryan-brotherhood", - "aryan-brotherhood-chat", - "aryan-brotherhood-messageboard", - "asian", - "asian-brides", - "ass-beating", - "ass2ass", - "ass2mouth", - "assault", - "assfucking", - "asshole-torn", - "audio", - "australian", - "australian-kangaroo-porn", - "awesome-real-headshot-vids", - "azn", - "babes", - "back-orifice", - "backdoor", - "backyard-accidents-gore", - "backyard-fireworks-disasters", - "bad-mixtape", - "badonkadonk", - "bang-women", - "banktransfer", - "banned-in-the-us", - "barely-legal", - "bargains", - "bbw", - "bdsm", - "beatdown", - "beatingwomen", - "begin-bank-account-xfer", - "beheading", - "best-deals", - "best-drugs", - "best-gore", - "best-pills", - "bettingonline", - "bigasses", - "bigbang", - "bigbutts", - "bigcashprize", - "bigcocks", - "biggest-cocks", - "biggest-tits", - "bigmilf", - "bigtits", - "bigwillie", - "bigwomen", - "bitcoin-2x", - "bitcoin-billionaire", - "bitcoin-cash-paydirt", - "bitcoin-miner", - "bitcoin-multiplier", - "blackmaildox", - "blood", - "bloody-murder", - "bodies", - "bodybuilders", - "boko-haram-beheading-vid", - "bombing", - "bomb-guide", - "bomb-instructions", - "bondage", - "boyfriendcamera", - "boyfriendphone", - "boyfriendtracker", - "brazilian", - "brazilian-fart-porn", - "build-muscle", - "bupropion", - "butts", - "buy-now", - "buttsex", - "bypass", - "cactus-inserted", - "cactus-sex", - "calcium", - "cashmoney", - "cashnow", - "casino", - "casino-loosest-slots", - "cats-being-beaten", - "cats-being-eaten", - "cat-torture", - "celebaddresses", - "celebphonenumbers", - "celebsextape", - "chatonline", - "chatwithbabes", - "cheapcheapcheap", - "cheap-guns-ammo", - "cheapcialis", - "cheapdrugs", - "cheappills", - "cheapviagra", - "cheat-the-system", - "children-murdered", - "child-molester-chat", - "chomo-chat", - "chomos-online", - "christian-murder", - "christian-murders-muslim", - "chrome-exploiter", - "chromium-supplement", - "cialis", - "classified-doxxx", - "classmates-of-sex", - "click", - "clickme", - "click-here", - "clickjack", - "clone-phone", - "clone-simcard", - "clownpenis", - "cobalt-supplement", - "cocaine", - "cockdock", - "cockfights", - "coinsonline", - "coin-multiplier", - "conspiracy", - "cookie", - "cookiestealer", - "coupons", - "cowgirl", - "crackcocaine", - "crack-bitcoin", - "crack-facebook", - "crack-passwords", - "craigslist-blowjob", - "craigslist-hookup", - "crappy-porn", - "crap-eating-contest", - "crazy-man-murder", - "creampie", - "creditcard", - "creditcard-numbers", - "creditscore", - "crime-tips", - "crypto", - "cummy", - "cum-harder", - "cuntboys-online", - "curbstomp-vids", - "cure-anything", - "cyberattack", - "cyberstalk", - "daesh", - "daeshmeetup", - "daeshrecruitment", - "daeshsignup", - "dailystormer", - "darkweb", - "date-hot-babes", - "date-hot-chix", - "date-hot-guys", - "date-hot-trans-babes", - "date-hot-trans-chix", - "date-hot-trans-guys", - "dating4oldppl", - "dead-animals", - "dead-children-pics", - "dead-people", - "deals", - "dealsdealsdeals", - "death", - "declassified", - "declassified-dox", - "deepfakes", - "deepweb", - "deepweb-drugs", - "deepweb-guns", - "diaperkink", - "diazepam", - "dick2dickdocking", - "dickenlargement", - "dickgirls-online", - "diet-supplement", - "digital", - "digitalcurrency", - "digitalpharmacy", - "dildoemporium", - "dmt-online", - "doctorrecommended", - "dogfights", - "dogsex", - "dogs-being-beaten", - "dogs-being-eaten", - "dog-torture", - "dollarforex", - "domesticabuse", - "donate", - "donkeycock", - "donkeyshow", - "dont-just-drizzle", - "dothisbytomorrow", - "dothisnow", - "doxxing", - "doxxx", - "drivebyshooting", - "drugs", - "dungeon", - "earn-ur-degree-online", - "easymen", - "easymoney", - "easywomen", - "echinacea", - "effexor", - "electionfraud", - "emailscam", - "endless-health", - "endless-money", - "enhancement", - "enter2win", - "escort", - "estradiol", - "estrogen", - "etherium", - "etherium-multiplier", - "euro-forex", - "evidence", - "exclusive-sextape", - "execution", - "exploit-begin", - "exploit-install", - "exploit-start", - "facebook-blowjob", - "facebook-hookups", - "facebook-of-sex", - "fakelogin", - "faminepics", - "fappeningpics", - "fart-porn", - "fast-weightloss-diet", - "fastremedy", - "fastweightloss", - "fentanyl", - "fent-online", - "fight-aging", - "finalmoments", - "finalsolution", - "fingering", - "fisting", - "flashplayer-exploit", - "flightpoints", - "footageofdeath", - "footfetish", - "footporn", - "foreign-brides", - "forex", - "forex-nobearmarket", - "forexinterbank", - "fraudalert", - "freakout", - "freeandroid", - "freeinternet", - "freeiphone", - "freemeds", - "freephone", - "freepills", - "freeporn", - "freeshows", - "freetv", - "freevirusremoval", - "freewebcams", - "french", - "friendster-of-sex", - "frottage", - "fucktonight", - "fucking-a-cat", - "fucking-dog", - "fucking-raw-chicken", - "funeral-gone-wrong", - "gangbang", - "gaping-asshole", - "gaping-pussy", - "gaysex", - "gay-old-men", - "gay-orgy", - "german", - "german-scat-porn", - "getagirl", - "getfuckedtonight", - "getjacked", - "getlaid", - "getlaidtonite", - "getpersonaldata", - "getrichovernight", - "getrichquick", - "getting-fucked", - "girlcock", - "girldick", - "girlfriendcamera", - "girlfriendphone", - "girlfriendtracker", - "github-of-sex", - "giveaway", - "gonesexual", - "gonewild", - "gonewrong", - "gorepix", - "governmentdocuments", - "governmentdox", - "government-fraud", - "gpstracking", - "graphicimages", - "grindrhookup", - "gruesome-gunshot-wounds", - "gunfightfootage", - "gunsonline", - "hacking-a-facebook", - "hateminorities", - "headshot", - "he-dies", - "hefuxher", - "helicoptercrash", - "herbalremedy", - "heroin", - "hijacker", - "hitler", - "hitler-sexfilm", - "holisticmedicine", - "hookers", - "hookers-near-u", - "homeless-man-murder", - "homeless-woman-murder", - "homelessdeath", - "homeopathic", - "hood-shooting", - "horny-dads", - "horny-goat-weed", - "horny-grandpas", - "horny-grannies", - "horny-men", - "horny-moms", - "horny-teens", - "horny-women", - "horse-sex", - "horse-slaughter", - "hotbabes", - "hotgoats", - "hotwomen", - "hotmail", - "how2printmoney", - "how2win", - "how2winatcasino", - "how-2-skin-a-gerbil", - "how-to-build-a-bomb", - "how-to-stop-immigration-for-good", - "hugecashprize", - "hugecocks", - "hypnosis", - "i-was-probed", - "idnumber", - "ie-exploiter", - "ied-footage", - "illegal", - "illegal-drugs", - "illegal-guns-4-sale", - "illegal-porno", - "iloveu", - "imake2000aweekathome", - "impersonateanyone", - "incestporn", - "incest-daughter-father", - "incest-daughter-mother", - "incest-mother-son", - "incest-father-son", - "increase-ur-e", - "increase-ur-t", - "install", - "install-exploit", - "install-keylogger", - "install-trojan", - "install-virus", - "instant-purchase", - "insurance-scam", - "interview", - "intifada", - "instagram-hack", - "instagram-of-sex", - "investmentopportunity", - "icloud-bypass", - "icloud-unlock", - "iphone-unlock", - "ip-finder", - "ip-hijacker", - "ip-stealer", - "iron-supplements", - "isis", - "isisrecruiter", - "isistrainingcamp", - "isis-beheading-vid", - "islam-murder", - "israel", - "iwasabductedbyaliens", - "jackedoff", - "jackingit", - "jackingoff", - "jackpot", - "jackpot-lottry-winner", - "jailbait", - "jailbeatdown", - "jailbreak", - "jailhouse-beatdown", - "jailhouse-murder", - "jailhouse-stabbing", - "jailmurder", - "jailstabbing", - "japanese", - "japanese-tentacle-porn", - "jar-inserted", - "jar-jar-porn", - "javascript-exploit", - "jelqing", - "jew-nwo", - "jewish-conspiracy", - "jewishbanks", - "jihad", - "jihadinusa", - "jizz", - "jizz-fountain", - "join-an-orgy", - "join-now", - "join-our-cult", - "join-us", - "joindaesh", - "joinisis", - "jointhearyanbrotherhood", - "jointhekkk", - "k9porn", - "keygen", - "keylogger", - "killallimmigrants", - "killchildren", - "killgays", - "killing", - "killwomen", - "kingscandal", - "kinkyporno", - "kiwifarms-chat", - "kiwifarms-messageboard", - "kkk", - "kkk-chat", - "kkk-messageboard", - "kkk-rallies-near-u", - "knifefight", - "krackapassword", - "krazy-deals", - "krazy-good-deal", - "leaked-documents", - "leaked-dox", - "legalbabes", - "legend-tits", - "legendary-growth", - "legendjackpot", - "lemonparty", - "lesbiansfuck", - "lesbian-gangbang", - "levitra", - "lexapro", - "linkedin-blowjob", - "linkedin-hookup", - "linkedin-of-sex", - "liveleak-for-lesbians", - "localmen", - "localwomen", - "loli-pics", - "lolicon-pics", - "lonelywomen", - "looseslots", - "loosesluts", - "lorazepam", - "lotto-winner", - "lsdcheap", - "m4m", - "m4t", - "m4w", - "magic-cure", - "magicmoney", - "magicmushrooms", - "magicweightloss", - "makehercum", - "makemoneyathome", - "makemoneydoingnothing", - "makemoneyfast", - "maleenhancement", - "malware", - "malwareinstaller", - "manybabes", - "marijuana", - "matureporn", - "mdmaonline", - "mdma-cheap", - "medical-magic-mushrooms", - "medical-mmj", - "medicalmushrooms", - "meds4cheap", - "medsfromcanada", - "medsfromchina", - "medsfromeurope", - "medsfromvanuatu", - "meet-men", - "meet-pedophiles", - "meet-scat-fetishists", - "meet-women", - "megajackpot", - "megatits", - "mein-kampf", - "mercenary", - "mercenary-kills-animals", - "mercenary-kills-kids", - "microsoft-giveaway", - "mike-pence-gay", - "mike-pence-naked", - "milf", - "milf-tits", - "military-death", - "mine-coins-for-free", - "mom-daughter-fuck", - "mom-has-sex-with-son", - "mommy-milkers", - "mongolian-throat-singing", - "monster-boners", - "monster-erection", - "morecash", - "moreincome", - "moremoney", - "morewins", - "more-cum", - "more-sex", - "msm-supplement", - "multivitamin", - "mushrooms", - "muslim", - "muslim-murders-christian", - "myspace-of-sex", - "my-tits-are-legend", - "nakedcelebs", - "naked-guys", - "naked-ladies", - "naked-old-men", - "naked-trans", - "nazi", - "nazi-chat", - "nazi-beatdown", - "newcure", - "newdrugs", - "newincome", - "newremedy", - "newworldorder", - "new-download-site", - "new-torrent-site", - "nftscam", - "nigeria-bank-transfer", - "no-consent", - "no-risk", - "nsfw", - "nudes", - "nwo", - "old-ladies", - "oldmangangbang", - "oldmen", - "oldremedy", - "one-weird-trick-to-lose-weight", - "online-horse-race", - "onlinebetting", - "onlinepharma", - "onlinepharmacy", - "only10dollars", - "openme", - "orangutansex", - "organ-selling", - "overnight", - "overnightcure", - "overnightgrowth", - "overnightwealth", - "overnightweightloss", - "overnitebillionaire", - "overnitemillionaire", - "overnitesuccess", - "overnitetrillionaire", - "palestinian-bombing", - "palestinian-murder", - "passwordhack", - "peanut-butter-sex", - "pee-everywhere", - "pee-sex", - "pedophilemeet", - "pedophilesonline", - "penetration", - "penisenlargement", - "petitmilf", - "pewdiepie-sextape", - "pharmacanada", - "pharmachina", - "pharmacy", - "pharmaeuro", - "pharmavanuatu", - "phishing", - "phonenumbers", - "phosphorous-supplements", - "physical-removal", - "picsofdeadanimals", - "picsofdeadpeople", - "pickup", - "pickup-girls", - "pigsex", - "pills", - "pills4cheap", - "pimping-guide", - "pipebomb", - "pipebomb-guide", - "pipebomb-instructions", - "piratedmovies", - "piratedmusic", - "piratedpodcasts", - "piratedshows", - "pirate-anything", - "pirate-games", - "pirate-movies", - "pirate-shows", - "pirate-windows", - "pizzagate", - "pokeronline", - "pokerrealmoney", - "policebodycam", - "poop-eating-contest", - "poop-everywhere", - "poopsex", - "popunder", - "popup", - "popup-spam", - "porn", - "prince-scandal", - "princess-scandal", - "probing", - "protein", - "proteinpowder", - "protocols-of-the-elders-of-zion", - "proud-boys-chat", - "proud-boys-racism-site", - "proud-boys-messageboard", - "prozac", - "psilocybin", - "psilocybin-online", - "publicsex", - "pussyfuck", - "putin-naked", - "putinsdick", - "qanonreveal", - "queen-nudes", - "queenscandal", - "quickie", - "quicklygetrich", - "quiturjob", - "racism-website", - "racist", - "racist-chat", - "racist-country-music", - "racist-dubstep", - "racist-folk-music", - "racist-grunge", - "racist-messageboard", - "racist-raps", - "racist-rock-music", - "racket", - "rapid-growth", - "rapid-weightloss", - "ratsex", - "ready2fuck", - "ready-for-sex", - "realcatsex", - "realdeath", - "realdogsex", - "realdonkeyshow", - "reallyhornygirls", - "refinance-now", - "refugee-murder", - "removevirus", - "richfast", - "richovernight", - "richquick", - "rickroll", - "ripoff", - "rippedfast", - "risk-free-investment", - "risperidol", - "rootkit", - "ropebondage", - "ropeporn", - "russian-bots", - "russian-brides", - "scary", - "scat-porn", - "school-shooting", - "scissoring", - "secretcams", - "secret-plans", - "secretary", - "see-me-naked", - "seeherwebcam", - "seemypussy", - "seemytits", - "sell-your-organs", - "seroquel", - "sexsexsex", - "sexoffender", - "sextape", - "sexting", - "sextwithgrannies", - "sextwithgrandpas", - "sexwithcats", - "sexwithdogs", - "sexwithgerbils", - "sexyladies", - "sexywomen", - "sex-slave-auction", - "shartporn", - "she-will-never-know", - "shedies", - "shefuxhim", - "shemale", - "shes-barely-legal", - "sheswaiting4u", - "shiteating", - "shitfountain", - "shitfucking", - "shocking", - "shocksite", - "shootingpix", - "shoplifting", - "shoplifting-tips", - "shotapics", - "shotacon-pics", - "sim-clone", - "sim-unlock", - "skinned-alive", - "skinned-cats", - "slutcams", - "sluts", - "smallcocks", - "snuff-films", - "social-security-scam", - "sodomy", - "soldering-iron-insertion", - "sourcecode-leak", - "spam4u", - "spambot", - "spotify-of-sex", - "spyware", - "spyonurboyfriend", - "spyonurgirlfriend", - "spyonurhusband", - "spyonurwife", - "spyware", - "ssn", - "stalkher", - "steal", - "stealfacebookpassword", - "stealgmailpassword", - "stealtwitterpassword", - "steal-bank-password", - "steal-without-getting-caught", - "stjohnswort", - "stolen-android", - "stolen-iphones", - "stoned-for-adultery", - "stoning-video", - "stop-immigration", - "stumping-vids", - "subwaydeath", - "super-boners", - "super-erection", - "super-nsfw", - "supplements", - "supplementscheap", - "suppository", - "sweepstakes", - "sweepstakes-enter2win", - "swissbankaccount", - "swisslottowinner", - "swissporn", - "t4m", - "t4t", - "t4w", - "taliban-interview", - "taliban-meetup", - "taliban-recruiter", - "teen", - "teen-barely-legal", - "telegram-adult-rooms", - "terrorist", - "testosterone", - "testosterone-supplement", - "the-truth-about-jews", - "thetruth", - "they-hurt-her", - "theycantstopyou", - "theyhatethis", - "threesomes", - "threesomes-near-u", - "tighten-my-pussy", - "tits", - "titstitstits", - "tokens", - "toolbar", - "toolbar-download", - "toolbar-install", - "toohot4tv", - "toosexy", - "toosexy4youtube", - "torrent-anything", - "torture-manual", - "torture-photos", - "torture-videos", - "totally-legit", - "totally-legit-hookups", - "totally-legit-vpns", - "trackmyex", - "trackmywife", - "train-accident-photos", - "trans-porn", - "trojaninstaller", - "trojan-start", - "turd-fucking", - "turkey-porn", - "twitter-blowjob", - "ugandan-porn-scam", - "ukranian-brides", - "unique-investment-opportunity", - "un-nwo-conspiracy", - "underground-death", - "unlocker4anything", - "ur-hubby-is-cumming", - "ur-jackpot-awaits", - "ur-wife-is-cumming", - "usb-hijacker", - "vaginapix", - "vaginal-rejeuvenation", - "vanadium-supplement", - "vanuatu-drugs", - "venereal-disease-pics", - "viagra", - "virusinstaller", - "virus-start", - "vitamin-b12", - "vitamin-b6", - "vitamin-c", - "vitamin-d", - "vitamin-e", - "vitamins", - "vomitfucking", - "vomitporn", - "vpnhacker", - "vulnexploit", - "w4m", - "w4t", - "w4w", - "warcrimes", - "warezinstall", - "warphotos", - "watch", - "watch-her-die", - "watch-her-get-fukd", - "watch-her-webcam", - "watch-him-die", - "watchmovies4free", - "watchpeopledie", - "watchtv4free", - "web-toolbar", - "weed", - "weightloss", - "wetfartporn", - "wetpussy", - "wfh", - "what-hes-doing", - "what-shes-doing", - "whattheydontwantu2know", - "whitepower", - "whos-my-husband-txting", - "whos-my-wife-txting", - "wifepussy", - "winatblackjack", - "winatpoker", - "winbig", - "winner-click-here", - "winning-lotto-numbers", - "woman-gets-stoned", - "womanbeaten", - "workathome", - "worldstar", - "worldstarfight", - "worm", - "worminstall", - "xtc-cheap", - "xxx-porn", - "xxx-pussy", - "xxx-tits", - "xxxtra", - "xylophone-sex", - "yenforex", - "you-won", - "youre-a-winner", - "yourip", - "yourssn", - "youtube-download", - "zinc-supplement", - "zebra-porn", - "zipper-open-pics", - "zoo", - "zooporn", -]); +mod strings { + use super::arr; -arr!(const EXT: [&str; _] = [ - ".avi", ".bas", ".csv", ".divx", ".dll", ".doc", ".docx", ".flv", ".gif", ".htm", ".html", - ".ini", ".jar", ".js", ".jpeg", ".jpg", ".m1v", ".m4a", ".mid", ".midi", ".mkv", ".mod", ".mov", - ".movie", ".mpa", ".mpe", ".mpeg", ".mpg", ".mp3", ".mp4", ".p7r", ".pdf", ".png", ".ppt", - ".pptx", ".rar", ".sgml", ".snd", ".swf", ".tiff", ".txt", ".webm", ".webp", ".vbs", ".xaf", - ".xhtml", ".xls", ".xlsx", ".xml", ".zip", -]); + arr!(pub(super) const NSFW: [&str; _] = [ + "---click-here---", + "---install-virus---", + "0percentartificial", + "0percentrisk", + "1$-iphone", + "1$-android", + "100percentlegal", + "100percentnatural", + "20percentoff", + "300deadmen", + "30waystokillyourwife", + "4chanhacks", + "419scam", + "420", + "500$-cashprize", + "69", + "7-11-robbery", + "800-dollars-4u", + "9-11-jumper", + "9-11-video", + "911-call", + "abuse", + "adblock-bypass", + "admin", + "ads", + "advert", + "affair", + "aids-bugcatching-parties", + "al-qaeda", + "al-qaeda-beheading-vids", + "al-qaeda-chat", + "al-qaeda-messageboard", + "al-qaeda-signup", + "alien-abduction", + "all-natural", + "ambien", + "anal", + "anal-penetration", + "analsex", + "ancient-cure", + "ancient-diet-pills", + "android-unlock", + "anti-aging-pills", + "anti-e-pills", + "anti-estrogen-pills", + "anti-t-pills", + "anti-testosterone-pills", + "antifa-kills-maga", + "antifamurder", + "antivirus", + "apple-giveaway", + "aryan-brotherhood", + "aryan-brotherhood-chat", + "aryan-brotherhood-messageboard", + "asian", + "asian-brides", + "ass-beating", + "ass2ass", + "ass2mouth", + "assault", + "assfucking", + "asshole-torn", + "audio", + "australian", + "australian-kangaroo-porn", + "awesome-real-headshot-vids", + "azn", + "babes", + "back-orifice", + "backdoor", + "backyard-accidents-gore", + "backyard-fireworks-disasters", + "bad-mixtape", + "badonkadonk", + "bang-women", + "banktransfer", + "banned-in-the-us", + "barely-legal", + "bargains", + "bbw", + "bdsm", + "beatdown", + "beatingwomen", + "begin-bank-account-xfer", + "beheading", + "best-deals", + "best-drugs", + "best-gore", + "best-pills", + "bettingonline", + "bigasses", + "bigbang", + "bigbutts", + "bigcashprize", + "bigcocks", + "biggest-cocks", + "biggest-tits", + "bigmilf", + "bigtits", + "bigwillie", + "bigwomen", + "bitcoin-2x", + "bitcoin-billionaire", + "bitcoin-cash-paydirt", + "bitcoin-miner", + "bitcoin-multiplier", + "blackmaildox", + "blood", + "bloody-murder", + "bodies", + "bodybuilders", + "boko-haram-beheading-vid", + "bombing", + "bomb-guide", + "bomb-instructions", + "bondage", + "boyfriendcamera", + "boyfriendphone", + "boyfriendtracker", + "brazilian", + "brazilian-fart-porn", + "build-muscle", + "bupropion", + "butts", + "buy-now", + "buttsex", + "bypass", + "cactus-inserted", + "cactus-sex", + "calcium", + "cashmoney", + "cashnow", + "casino", + "casino-loosest-slots", + "cats-being-beaten", + "cats-being-eaten", + "cat-torture", + "celebaddresses", + "celebphonenumbers", + "celebsextape", + "chatonline", + "chatwithbabes", + "cheapcheapcheap", + "cheap-guns-ammo", + "cheapcialis", + "cheapdrugs", + "cheappills", + "cheapviagra", + "cheat-the-system", + "children-murdered", + "child-molester-chat", + "chomo-chat", + "chomos-online", + "christian-murder", + "christian-murders-muslim", + "chrome-exploiter", + "chromium-supplement", + "cialis", + "classified-doxxx", + "classmates-of-sex", + "click", + "clickme", + "click-here", + "clickjack", + "clone-phone", + "clone-simcard", + "clownpenis", + "cobalt-supplement", + "cocaine", + "cockdock", + "cockfights", + "coinsonline", + "coin-multiplier", + "conspiracy", + "cookie", + "cookiestealer", + "coupons", + "cowgirl", + "crackcocaine", + "crack-bitcoin", + "crack-facebook", + "crack-passwords", + "craigslist-blowjob", + "craigslist-hookup", + "crappy-porn", + "crap-eating-contest", + "crazy-man-murder", + "creampie", + "creditcard", + "creditcard-numbers", + "creditscore", + "crime-tips", + "crypto", + "cummy", + "cum-harder", + "cuntboys-online", + "curbstomp-vids", + "cure-anything", + "cyberattack", + "cyberstalk", + "daesh", + "daeshmeetup", + "daeshrecruitment", + "daeshsignup", + "dailystormer", + "darkweb", + "date-hot-babes", + "date-hot-chix", + "date-hot-guys", + "date-hot-trans-babes", + "date-hot-trans-chix", + "date-hot-trans-guys", + "dating4oldppl", + "dead-animals", + "dead-children-pics", + "dead-people", + "deals", + "dealsdealsdeals", + "death", + "declassified", + "declassified-dox", + "deepfakes", + "deepweb", + "deepweb-drugs", + "deepweb-guns", + "diaperkink", + "diazepam", + "dick2dickdocking", + "dickenlargement", + "dickgirls-online", + "diet-supplement", + "digital", + "digitalcurrency", + "digitalpharmacy", + "dildoemporium", + "dmt-online", + "doctorrecommended", + "dogfights", + "dogsex", + "dogs-being-beaten", + "dogs-being-eaten", + "dog-torture", + "dollarforex", + "domesticabuse", + "donate", + "donkeycock", + "donkeyshow", + "dont-just-drizzle", + "dothisbytomorrow", + "dothisnow", + "doxxing", + "doxxx", + "drivebyshooting", + "drugs", + "dungeon", + "earn-ur-degree-online", + "easymen", + "easymoney", + "easywomen", + "echinacea", + "effexor", + "electionfraud", + "emailscam", + "endless-health", + "endless-money", + "enhancement", + "enter2win", + "escort", + "estradiol", + "estrogen", + "etherium", + "etherium-multiplier", + "euro-forex", + "evidence", + "exclusive-sextape", + "execution", + "exploit-begin", + "exploit-install", + "exploit-start", + "facebook-blowjob", + "facebook-hookups", + "facebook-of-sex", + "fakelogin", + "faminepics", + "fappeningpics", + "fart-porn", + "fast-weightloss-diet", + "fastremedy", + "fastweightloss", + "fentanyl", + "fent-online", + "fight-aging", + "finalmoments", + "finalsolution", + "fingering", + "fisting", + "flashplayer-exploit", + "flightpoints", + "footageofdeath", + "footfetish", + "footporn", + "foreign-brides", + "forex", + "forex-nobearmarket", + "forexinterbank", + "fraudalert", + "freakout", + "freeandroid", + "freeinternet", + "freeiphone", + "freemeds", + "freephone", + "freepills", + "freeporn", + "freeshows", + "freetv", + "freevirusremoval", + "freewebcams", + "french", + "friendster-of-sex", + "frottage", + "fucktonight", + "fucking-a-cat", + "fucking-dog", + "fucking-raw-chicken", + "funeral-gone-wrong", + "gangbang", + "gaping-asshole", + "gaping-pussy", + "gaysex", + "gay-old-men", + "gay-orgy", + "german", + "german-scat-porn", + "getagirl", + "getfuckedtonight", + "getjacked", + "getlaid", + "getlaidtonite", + "getpersonaldata", + "getrichovernight", + "getrichquick", + "getting-fucked", + "girlcock", + "girldick", + "girlfriendcamera", + "girlfriendphone", + "girlfriendtracker", + "github-of-sex", + "giveaway", + "gonesexual", + "gonewild", + "gonewrong", + "gorepix", + "governmentdocuments", + "governmentdox", + "government-fraud", + "gpstracking", + "graphicimages", + "grindrhookup", + "gruesome-gunshot-wounds", + "gunfightfootage", + "gunsonline", + "hacking-a-facebook", + "hateminorities", + "headshot", + "he-dies", + "hefuxher", + "helicoptercrash", + "herbalremedy", + "heroin", + "hijacker", + "hitler", + "hitler-sexfilm", + "holisticmedicine", + "hookers", + "hookers-near-u", + "homeless-man-murder", + "homeless-woman-murder", + "homelessdeath", + "homeopathic", + "hood-shooting", + "horny-dads", + "horny-goat-weed", + "horny-grandpas", + "horny-grannies", + "horny-men", + "horny-moms", + "horny-teens", + "horny-women", + "horse-sex", + "horse-slaughter", + "hotbabes", + "hotgoats", + "hotwomen", + "hotmail", + "how2printmoney", + "how2win", + "how2winatcasino", + "how-2-skin-a-gerbil", + "how-to-build-a-bomb", + "how-to-stop-immigration-for-good", + "hugecashprize", + "hugecocks", + "hypnosis", + "i-was-probed", + "idnumber", + "ie-exploiter", + "ied-footage", + "illegal", + "illegal-drugs", + "illegal-guns-4-sale", + "illegal-porno", + "iloveu", + "imake2000aweekathome", + "impersonateanyone", + "incestporn", + "incest-daughter-father", + "incest-daughter-mother", + "incest-mother-son", + "incest-father-son", + "increase-ur-e", + "increase-ur-t", + "install", + "install-exploit", + "install-keylogger", + "install-trojan", + "install-virus", + "instant-purchase", + "insurance-scam", + "interview", + "intifada", + "instagram-hack", + "instagram-of-sex", + "investmentopportunity", + "icloud-bypass", + "icloud-unlock", + "iphone-unlock", + "ip-finder", + "ip-hijacker", + "ip-stealer", + "iron-supplements", + "isis", + "isisrecruiter", + "isistrainingcamp", + "isis-beheading-vid", + "islam-murder", + "israel", + "iwasabductedbyaliens", + "jackedoff", + "jackingit", + "jackingoff", + "jackpot", + "jackpot-lottry-winner", + "jailbait", + "jailbeatdown", + "jailbreak", + "jailhouse-beatdown", + "jailhouse-murder", + "jailhouse-stabbing", + "jailmurder", + "jailstabbing", + "japanese", + "japanese-tentacle-porn", + "jar-inserted", + "jar-jar-porn", + "javascript-exploit", + "jelqing", + "jew-nwo", + "jewish-conspiracy", + "jewishbanks", + "jihad", + "jihadinusa", + "jizz", + "jizz-fountain", + "join-an-orgy", + "join-now", + "join-our-cult", + "join-us", + "joindaesh", + "joinisis", + "jointhearyanbrotherhood", + "jointhekkk", + "k9porn", + "keygen", + "keylogger", + "killallimmigrants", + "killchildren", + "killgays", + "killing", + "killwomen", + "kingscandal", + "kinkyporno", + "kiwifarms-chat", + "kiwifarms-messageboard", + "kkk", + "kkk-chat", + "kkk-messageboard", + "kkk-rallies-near-u", + "knifefight", + "krackapassword", + "krazy-deals", + "krazy-good-deal", + "leaked-documents", + "leaked-dox", + "legalbabes", + "legend-tits", + "legendary-growth", + "legendjackpot", + "lemonparty", + "lesbiansfuck", + "lesbian-gangbang", + "levitra", + "lexapro", + "linkedin-blowjob", + "linkedin-hookup", + "linkedin-of-sex", + "liveleak-for-lesbians", + "localmen", + "localwomen", + "loli-pics", + "lolicon-pics", + "lonelywomen", + "looseslots", + "loosesluts", + "lorazepam", + "lotto-winner", + "lsdcheap", + "m4m", + "m4t", + "m4w", + "magic-cure", + "magicmoney", + "magicmushrooms", + "magicweightloss", + "makehercum", + "makemoneyathome", + "makemoneydoingnothing", + "makemoneyfast", + "maleenhancement", + "malware", + "malwareinstaller", + "manybabes", + "marijuana", + "matureporn", + "mdmaonline", + "mdma-cheap", + "medical-magic-mushrooms", + "medical-mmj", + "medicalmushrooms", + "meds4cheap", + "medsfromcanada", + "medsfromchina", + "medsfromeurope", + "medsfromvanuatu", + "meet-men", + "meet-pedophiles", + "meet-scat-fetishists", + "meet-women", + "megajackpot", + "megatits", + "mein-kampf", + "mercenary", + "mercenary-kills-animals", + "mercenary-kills-kids", + "microsoft-giveaway", + "mike-pence-gay", + "mike-pence-naked", + "milf", + "milf-tits", + "military-death", + "mine-coins-for-free", + "mom-daughter-fuck", + "mom-has-sex-with-son", + "mommy-milkers", + "mongolian-throat-singing", + "monster-boners", + "monster-erection", + "morecash", + "moreincome", + "moremoney", + "morewins", + "more-cum", + "more-sex", + "msm-supplement", + "multivitamin", + "mushrooms", + "muslim", + "muslim-murders-christian", + "myspace-of-sex", + "my-tits-are-legend", + "nakedcelebs", + "naked-guys", + "naked-ladies", + "naked-old-men", + "naked-trans", + "nazi", + "nazi-chat", + "nazi-beatdown", + "newcure", + "newdrugs", + "newincome", + "newremedy", + "newworldorder", + "new-download-site", + "new-torrent-site", + "nftscam", + "nigeria-bank-transfer", + "no-consent", + "no-risk", + "nsfw", + "nudes", + "nwo", + "old-ladies", + "oldmangangbang", + "oldmen", + "oldremedy", + "one-weird-trick-to-lose-weight", + "online-horse-race", + "onlinebetting", + "onlinepharma", + "onlinepharmacy", + "only10dollars", + "openme", + "orangutansex", + "organ-selling", + "overnight", + "overnightcure", + "overnightgrowth", + "overnightwealth", + "overnightweightloss", + "overnitebillionaire", + "overnitemillionaire", + "overnitesuccess", + "overnitetrillionaire", + "palestinian-bombing", + "palestinian-murder", + "passwordhack", + "peanut-butter-sex", + "pee-everywhere", + "pee-sex", + "pedophilemeet", + "pedophilesonline", + "penetration", + "penisenlargement", + "petitmilf", + "pewdiepie-sextape", + "pharmacanada", + "pharmachina", + "pharmacy", + "pharmaeuro", + "pharmavanuatu", + "phishing", + "phonenumbers", + "phosphorous-supplements", + "physical-removal", + "picsofdeadanimals", + "picsofdeadpeople", + "pickup", + "pickup-girls", + "pigsex", + "pills", + "pills4cheap", + "pimping-guide", + "pipebomb", + "pipebomb-guide", + "pipebomb-instructions", + "piratedmovies", + "piratedmusic", + "piratedpodcasts", + "piratedshows", + "pirate-anything", + "pirate-games", + "pirate-movies", + "pirate-shows", + "pirate-windows", + "pizzagate", + "pokeronline", + "pokerrealmoney", + "policebodycam", + "poop-eating-contest", + "poop-everywhere", + "poopsex", + "popunder", + "popup", + "popup-spam", + "porn", + "prince-scandal", + "princess-scandal", + "probing", + "protein", + "proteinpowder", + "protocols-of-the-elders-of-zion", + "proud-boys-chat", + "proud-boys-racism-site", + "proud-boys-messageboard", + "prozac", + "psilocybin", + "psilocybin-online", + "publicsex", + "pussyfuck", + "putin-naked", + "putinsdick", + "qanonreveal", + "queen-nudes", + "queenscandal", + "quickie", + "quicklygetrich", + "quiturjob", + "racism-website", + "racist", + "racist-chat", + "racist-country-music", + "racist-dubstep", + "racist-folk-music", + "racist-grunge", + "racist-messageboard", + "racist-raps", + "racist-rock-music", + "racket", + "rapid-growth", + "rapid-weightloss", + "ratsex", + "ready2fuck", + "ready-for-sex", + "realcatsex", + "realdeath", + "realdogsex", + "realdonkeyshow", + "reallyhornygirls", + "refinance-now", + "refugee-murder", + "removevirus", + "richfast", + "richovernight", + "richquick", + "rickroll", + "ripoff", + "rippedfast", + "risk-free-investment", + "risperidol", + "rootkit", + "ropebondage", + "ropeporn", + "russian-bots", + "russian-brides", + "scary", + "scat-porn", + "school-shooting", + "scissoring", + "secretcams", + "secret-plans", + "secretary", + "see-me-naked", + "seeherwebcam", + "seemypussy", + "seemytits", + "sell-your-organs", + "seroquel", + "sexsexsex", + "sexoffender", + "sextape", + "sexting", + "sextwithgrannies", + "sextwithgrandpas", + "sexwithcats", + "sexwithdogs", + "sexwithgerbils", + "sexyladies", + "sexywomen", + "sex-slave-auction", + "shartporn", + "she-will-never-know", + "shedies", + "shefuxhim", + "shemale", + "shes-barely-legal", + "sheswaiting4u", + "shiteating", + "shitfountain", + "shitfucking", + "shocking", + "shocksite", + "shootingpix", + "shoplifting", + "shoplifting-tips", + "shotapics", + "shotacon-pics", + "sim-clone", + "sim-unlock", + "skinned-alive", + "skinned-cats", + "slutcams", + "sluts", + "smallcocks", + "snuff-films", + "social-security-scam", + "sodomy", + "soldering-iron-insertion", + "sourcecode-leak", + "spam4u", + "spambot", + "spotify-of-sex", + "spyware", + "spyonurboyfriend", + "spyonurgirlfriend", + "spyonurhusband", + "spyonurwife", + "spyware", + "ssn", + "stalkher", + "steal", + "stealfacebookpassword", + "stealgmailpassword", + "stealtwitterpassword", + "steal-bank-password", + "steal-without-getting-caught", + "stjohnswort", + "stolen-android", + "stolen-iphones", + "stoned-for-adultery", + "stoning-video", + "stop-immigration", + "stumping-vids", + "subwaydeath", + "super-boners", + "super-erection", + "super-nsfw", + "supplements", + "supplementscheap", + "suppository", + "sweepstakes", + "sweepstakes-enter2win", + "swissbankaccount", + "swisslottowinner", + "swissporn", + "t4m", + "t4t", + "t4w", + "taliban-interview", + "taliban-meetup", + "taliban-recruiter", + "teen", + "teen-barely-legal", + "telegram-adult-rooms", + "terrorist", + "testosterone", + "testosterone-supplement", + "the-truth-about-jews", + "thetruth", + "they-hurt-her", + "theycantstopyou", + "theyhatethis", + "threesomes", + "threesomes-near-u", + "tighten-my-pussy", + "tits", + "titstitstits", + "tokens", + "toolbar", + "toolbar-download", + "toolbar-install", + "toohot4tv", + "toosexy", + "toosexy4youtube", + "torrent-anything", + "torture-manual", + "torture-photos", + "torture-videos", + "totally-legit", + "totally-legit-hookups", + "totally-legit-vpns", + "trackmyex", + "trackmywife", + "train-accident-photos", + "trans-porn", + "trojaninstaller", + "trojan-start", + "turd-fucking", + "turkey-porn", + "twitter-blowjob", + "ugandan-porn-scam", + "ukranian-brides", + "unique-investment-opportunity", + "un-nwo-conspiracy", + "underground-death", + "unlocker4anything", + "ur-hubby-is-cumming", + "ur-jackpot-awaits", + "ur-wife-is-cumming", + "usb-hijacker", + "vaginapix", + "vaginal-rejeuvenation", + "vanadium-supplement", + "vanuatu-drugs", + "venereal-disease-pics", + "viagra", + "virusinstaller", + "virus-start", + "vitamin-b12", + "vitamin-b6", + "vitamin-c", + "vitamin-d", + "vitamin-e", + "vitamins", + "vomitfucking", + "vomitporn", + "vpnhacker", + "vulnexploit", + "w4m", + "w4t", + "w4w", + "warcrimes", + "warezinstall", + "warphotos", + "watch", + "watch-her-die", + "watch-her-get-fukd", + "watch-her-webcam", + "watch-him-die", + "watchmovies4free", + "watchpeopledie", + "watchtv4free", + "web-toolbar", + "weed", + "weightloss", + "wetfartporn", + "wetpussy", + "wfh", + "what-hes-doing", + "what-shes-doing", + "whattheydontwantu2know", + "whitepower", + "whos-my-husband-txting", + "whos-my-wife-txting", + "wifepussy", + "winatblackjack", + "winatpoker", + "winbig", + "winner-click-here", + "winning-lotto-numbers", + "woman-gets-stoned", + "womanbeaten", + "workathome", + "worldstar", + "worldstarfight", + "worm", + "worminstall", + "xtc-cheap", + "xxx-porn", + "xxx-pussy", + "xxx-tits", + "xxxtra", + "xylophone-sex", + "yenforex", + "you-won", + "youre-a-winner", + "yourip", + "yourssn", + "youtube-download", + "zinc-supplement", + "zebra-porn", + "zipper-open-pics", + "zoo", + "zooporn", + ]); -arr!(const EXT_EXE: [&str; _] = [ - ".app", ".bat", ".dmg", ".exe", ".msi", ".run", ".script", -]); + arr!(pub(super) const EXT: [&str; _] = [ + ".avi", ".bas", ".csv", ".divx", ".dll", ".doc", ".docx", ".flv", ".gif", ".htm", ".html", + ".ini", ".jar", ".js", ".jpeg", ".jpg", ".m1v", ".m4a", ".mid", ".midi", ".mkv", ".mod", ".mov", + ".movie", ".mpa", ".mpe", ".mpeg", ".mpg", ".mp3", ".mp4", ".p7r", ".pdf", ".png", ".ppt", + ".pptx", ".rar", ".sgml", ".snd", ".swf", ".tiff", ".txt", ".webm", ".webp", ".vbs", ".xaf", + ".xhtml", ".xls", ".xlsx", ".xml", ".zip", + ]); + + arr!(pub(super) const EXT_EXE: [&str; _] = [ + ".app", ".bat", ".dmg", ".exe", ".msi", ".run", ".script", + ]); +} diff --git a/src/loadenv.rs b/src/loadenv.rs deleted file mode 100644 index c326806..0000000 --- a/src/loadenv.rs +++ /dev/null @@ -1,124 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/loadenv.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::{path::PathBuf, str::FromStr}; - -use anyhow::Result; -use argon2_kdf::Hash; -use axum_client_ip::SecureClientIpSource; -use base64ct::{Base64Unpadded, Encoding}; -use dotenvy::dotenv; -use envy::from_env; -use serde::{ - de::{Deserializer, Error}, - Deserialize, -}; - -use tracing::Level; - -fn default_sitename() -> String { - "ShadyURL".to_string() -} - -fn default_pid_file() -> PathBuf { - PathBuf::from("/var/run/shadyurl.pid") -} - -fn default_log_level() -> Level { - Level::INFO -} - -fn deserialize_secret<'de, D>(d: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s = String::deserialize(d)?; - let vec = Base64Unpadded::decode_vec(&s) - .map_err(|e| Error::custom(format!("Invalid base64 value: {e}")))?; - Ok(vec) -} - -fn deserialize_hash<'de, D>(d: D) -> Result -where - D: Deserializer<'de>, -{ - let s = String::deserialize(d)?; - let hash = Hash::from_str(s.as_str()) - .map_err(|e| Error::custom(format!("Invalid hash value: {e}")))?; - Ok(hash) -} - -fn deserialize_level<'de, D>(d: D) -> Result -where - D: Deserializer<'de>, -{ - let s = u8::deserialize(d)?; - match s { - 0 => Ok(Level::ERROR), - 1 => Ok(Level::WARN), - 2 => Ok(Level::INFO), - 3 => Ok(Level::DEBUG), - 4 => Ok(Level::TRACE), - _ => Err(Error::custom( - "Invalid tracing level, must be 0-4".to_string(), - )), - } -} - -pub(crate) fn load_env() -> Result { - dotenv()?; - let env: EnvVars = from_env()?; - Ok(env) -} - -#[derive(Deserialize)] -pub(crate) struct EnvVars { - pub(crate) shady_host: String, - - // XXX should default to shady_host, but how? - pub(crate) base_host: String, - - #[serde(default = "default_sitename")] - pub(crate) sitename: String, - - pub(crate) bind: String, - pub(crate) ip_source: SecureClientIpSource, - pub(crate) database_url: String, - pub(crate) redis_url: String, - pub(crate) username: String, - - #[serde(deserialize_with = "deserialize_hash")] - pub(crate) password_hash: Hash, - - #[serde(deserialize_with = "deserialize_secret")] - pub(crate) secret_key: Vec, - - #[serde(deserialize_with = "deserialize_level", default = "default_log_level")] - pub(crate) log_level: Level, - - #[serde(default)] - pub(crate) log_stderr: bool, - - #[serde(default)] - pub(crate) daemon: bool, - - #[serde(default = "default_pid_file")] - pub(crate) pid_file: PathBuf, - - #[serde(default)] - pub(crate) daemon_user: Option, - - #[serde(default)] - pub(crate) daemon_group: Option, -} diff --git a/src/logging.rs b/src/logging.rs deleted file mode 100644 index 61b7c83..0000000 --- a/src/logging.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/logging.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::ffi::CStr; - -use syslog_tracing::{Facility, Options, Syslog}; - -use crate::loadenv::EnvVars; - -pub(crate) fn setup_logger(env: &EnvVars) { - let identity: &CStr = CStr::from_bytes_with_nul(b"shadyurl-rust\0").unwrap(); - - let mut options: Options = Options::LOG_NDELAY | Options::LOG_PID | Options::LOG_CONS; - if env.log_stderr { - options = options | Options::LOG_PERROR; - } - - let facility: Facility = Facility::Daemon; - let syslog: Syslog = Syslog::new(identity, options, facility).unwrap(); - - tracing_subscriber::fmt() - .with_ansi(false) - .with_max_level(env.log_level) - .with_writer(syslog) - .init(); -} diff --git a/src/main.rs b/src/main.rs index 1067885..1fd9bef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,137 +12,175 @@ * work. If not, see . */ -use std::{fs::File, net::SocketAddr}; - -use anyhow::Result; -use axum::Server; -use nix::unistd::ftruncate; -use proctitle::set_title; -use tokio::signal; -use tracing::error; +#![warn(unused_extern_crates)] +#![warn(clippy::pedantic)] +#![warn(clippy::nursery)] +#![warn(clippy::cargo)] mod auth; -mod controllers; -mod daemon; -mod database; -mod err; +mod csrf; +mod env; +mod error_response; mod generate; -mod loadenv; -mod logging; -mod router; mod state; -mod templates; mod util; mod validators; +mod web; -use crate::{ - daemon::{close_stdio, drop_privileges, open_pid_file, set_umask, to_background}, - database::get_db, - loadenv::EnvVars, - logging::setup_logger, - router::get_router, - state::AppState, -}; - -// XXX passing in the PID file here is a hack -async fn shutdown_signal(pid_file: &mut File) { - let ctrl_c = async { - signal::ctrl_c() - .await - .expect("failed to install Ctrl+C/SIGINT handler"); - }; - - let alarm = async { - signal::unix::signal(signal::unix::SignalKind::alarm()) - .expect("failed to install SIGALRM handler") - .recv() - .await; - }; - - let hangup = async { - signal::unix::signal(signal::unix::SignalKind::hangup()) - .expect("failed to install SIGHUP handler") - .recv() - .await; - }; - - let terminate = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("failed to install SIGTERM handler") - .recv() - .await; - }; - - // Maybe use this for stats later? - let user_defined1 = async { - signal::unix::signal(signal::unix::SignalKind::user_defined1()) - .expect("failed to install SIGUSR1 handler") - .recv() - .await; - }; - - let user_defined2 = async { - signal::unix::signal(signal::unix::SignalKind::user_defined2()) - .expect("failed to install SIGUSR2 handler") - .recv() - .await; - }; - - tokio::select! { - _ = ctrl_c => {}, - _ = alarm => {}, - _ = hangup => {}, - _ = terminate => {}, - _ = user_defined1 => {}, - _ = user_defined2 => {} - } +use std::io::{prelude::*, stdin, stdout}; + +use clap::{Parser, Subcommand}; +use password_auth::generate_hash; +use rpassword::prompt_password; +use sea_orm::{ConnectOptions, Database}; +use tracing::log::LevelFilter; + +use crate::{env::Vars, web::App}; - error!("signal received, starting graceful shutdown"); +use migration::{Migrator, MigratorTrait}; +use service::{Mutation, Query}; - // Clear PID file - let _ = ftruncate(pid_file, 0); +#[derive(Parser)] +struct Cli { + #[command(subcommand)] + command: Option, } -// We must fork before we do anything else. -// We might as well do other environmental init stuff too. -fn main() -> Result<()> { - let env = loadenv::load_env()?; +#[derive(Subcommand)] +enum Commands { + Run, + AddUser { username: String }, + DeleteUser { username: String }, + ChangePassword { username: String }, +} + +#[derive(Debug, thiserror::Error)] +enum UserError { + #[error("Could not alter user {}: {}", .0, .1)] + Change(String, String), + + #[error("Could not create user {}: {}", .0, .1)] + Add(String, String), - if env.daemon { - // Tokio can't survive a fork. This MUST be done first. - to_background()?; - close_stdio()?; + #[error("Could not delete user {}: {}", .0, .1)] + Delete(String, String), +} + +async fn add_user_cli(username: &str) -> Result<(), Box> { + let env = Vars::load_env()?; + let mut opt = ConnectOptions::new(&env.database_url); + opt.sqlx_logging(false) + .sqlx_logging_level(LevelFilter::Warn); + + let db = Database::connect(opt).await?; + + Migrator::up(&db, None).await?; + + let mut password = prompt_password("Password:")?; + if password != prompt_password("Repeat password:")? { + eprintln!("Passwords do not match"); + return Err(Box::new(UserError::Add( + username.to_string(), + "Passwords did not match".to_string(), + ))); } - set_umask(); + password = generate_hash(password); - let mut pid_file = open_pid_file(&env)?; + Mutation::create_user(&db, username, &password).await?; - setup_logger(&env); + Ok(()) +} + +async fn delete_user_cli(username: &str) -> Result<(), Box> { + let env = Vars::load_env()?; + let mut opt = ConnectOptions::new(&env.database_url); + opt.sqlx_logging(false) + .sqlx_logging_level(LevelFilter::Warn); + + let db = Database::connect(opt).await?; + + Migrator::up(&db, None).await?; + + let mut response = String::new(); + loop { + print!("Are you SURE you want to delete user {username}? [yes/no] "); + stdout().flush()?; + stdin().lock().read_line(&mut response)?; + response = response.trim_end().to_ascii_lowercase().to_string(); + match response.as_str() { + "no" | "n" => { + return Err(Box::new(UserError::Delete( + username.to_string(), + "Aborted".to_string(), + ))) + } + "yes" => break, + _ => { + response.clear(); + println!("Please type yes or no."); + } + } + } - set_title("shadyurl-rust"); + let user = Query::find_user_by_username(&db, username) + .await? + .ok_or_else(|| UserError::Delete(username.to_string(), "Username not found".to_string()))?; - tokio_main(&env, &mut pid_file) + Mutation::delete_user(&db, user.id).await?; + + Ok(()) } -#[tokio::main] -async fn tokio_main(env: &EnvVars, pid_file: &mut File) -> Result<()> { - let db = get_db(env).await?; +async fn change_password_cli(username: &str) -> Result<(), Box> { + let env = Vars::load_env()?; + let mut opt = ConnectOptions::new(&env.database_url); + opt.sqlx_logging(false) + .sqlx_logging_level(LevelFilter::Warn); - let state = AppState::new_from_env(db, env); + let db = Database::connect(opt).await?; - let app = get_router(env, state).await?; - let server = Server::try_bind(&env.bind.parse()?)? - .serve(app.into_make_service_with_connect_info::()) - .with_graceful_shutdown(shutdown_signal(pid_file)); + Migrator::up(&db, None).await?; - // We can only do this after the above - drop_privileges(env)?; + let mut password = prompt_password("Password:")?; + if password != prompt_password("Repeat password:")? { + eprintln!("Passwords do not match"); + return Err(Box::new(UserError::Change( + username.to_string(), + "Passwords did not match".to_string(), + ))); + } - server.await?; + password = generate_hash(password); - // FIXME - do we actually get here? - let _ = ftruncate(pid_file, 0); + Mutation::change_user_password(&db, username, &password).await?; Ok(()) } + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber::fmt().compact().init(); + + let cli = Cli::parse(); + match &cli.command { + Some(Commands::AddUser { username }) => { + add_user_cli(username).await?; + println!("Success! User {username} added"); + return Ok(()); + } + Some(Commands::DeleteUser { username }) => { + delete_user_cli(username).await?; + println!("Success! User {username} deleted"); + return Ok(()); + } + Some(Commands::ChangePassword { username }) => { + change_password_cli(username).await?; + println!("Success! Password for {username} changed"); + return Ok(()); + } + Some(Commands::Run) | None => {} + } + + App::new().await?.serve().await +} diff --git a/src/router.rs b/src/router.rs deleted file mode 100644 index 5fe7f67..0000000 --- a/src/router.rs +++ /dev/null @@ -1,117 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/router.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use std::{collections::HashMap, sync::Arc}; - -use anyhow::{anyhow, Error, Result}; -use async_fred_session::{ - fred::{pool::RedisPool, types::RedisConfig}, - RedisSessionStore, -}; -use axum::{ - error_handling::HandleErrorLayer, - middleware::from_fn_with_state, - routing::{get, post}, - Router, -}; -use axum_csrf::{CsrfConfig, CsrfLayer, Key}; -use axum_login::{ - axum_sessions::SessionLayer, memory_store::MemoryStore as AuthMemoryStore, AuthLayer, AuthUser, -}; -use tokio::{sync::RwLock, time::Duration}; -use tower::ServiceBuilder; -use tower_http::services::{ServeDir, ServeFile}; - -use crate::{ - auth::{RequireAuth, User}, - controllers::{ - accept_form, admin_handler, delete_handler, get_shady, handle_timeout_error, login_handler, - login_page_handler, logout_handler, root, transform_error, - }, - loadenv::EnvVars, - AppState, -}; - -async fn create_session_layer(env: &EnvVars) -> Result> { - let redis_config = RedisConfig::from_url(env.redis_url.as_str()) - .map_err(|e| Error::new(e).context("Failed to parse Redis URL"))?; - let rds_pool = RedisPool::new(redis_config, None, None, 6).unwrap(); - rds_pool.connect(); - rds_pool - .wait_for_connect() - .await - .map_err(|e| Error::new(e).context("Could not connect to redis"))?; - - let cookie_domain = env - .base_host - .split(':') - .next() - .ok_or(anyhow!("Failed to parse cookie domain"))?; - let session_store = RedisSessionStore::from_pool(rds_pool, Some("async-fred-session/".into())); - Ok(SessionLayer::new(session_store, &env.secret_key).with_cookie_domain(cookie_domain)) -} - -async fn create_auth_layer( - env: &EnvVars, - user: &User, -) -> AuthLayer, usize, User> { - let store = Arc::new(RwLock::new(HashMap::default())); - - store.write().await.insert(user.get_id(), user.clone()); - - let user_store = AuthMemoryStore::new(&store); - AuthLayer::new(user_store, &env.secret_key) -} - -pub(crate) async fn get_router(env: &EnvVars, state: AppState) -> Result { - let session_layer = create_session_layer(env).await?; - let auth_layer = create_auth_layer(env, &state.user).await; - - let cookie_key = Key::from(&env.secret_key); - let csrf_config = CsrfConfig::default().with_key(Some(cookie_key)); - - let services = ServiceBuilder::new() - .layer(HandleErrorLayer::new(handle_timeout_error)) - .timeout(Duration::from_secs(10)) - .layer(env.ip_source.clone().into_extension()) - .layer(CsrfLayer::new(csrf_config)) - .layer(session_layer) - .layer(auth_layer); - - let login_router = Router::new() - .route("/login", get(login_page_handler).post(login_handler)) - .route("/logout", get(logout_handler)); - - let admin_router = Router::new() - .route("/admin", get(admin_handler).post(delete_handler)) - .route_layer(RequireAuth::login()); - - let shady_router = Router::new() - .route("/makeurl", post(accept_form)) - .nest_service("/robots.txt", ServeFile::new("static/robots.txt")) - .nest_service("/ads.txt", ServeFile::new("static/robots.txt")) - .nest_service("/app-ads.txt", ServeFile::new("static/robots.txt")) - .nest_service("/favicon.ico", ServeFile::new("static/favicon.ico")) - .nest_service("/assets", ServeDir::new("static/assets")) - .route("/*shady", get(get_shady)) - .route("/", get(root)) - .route_layer(from_fn_with_state(state.clone(), transform_error)); - - Ok(Router::new() - .merge(login_router) - .merge(admin_router) - .merge(shady_router) - .layer(services) - .with_state(state)) -} diff --git a/src/state.rs b/src/state.rs index b3abc72..6877b56 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,33 +14,15 @@ use std::sync::Arc; -use sea_orm::DatabaseConnection; +use csrf::ChaCha20Poly1305CsrfProtection; +use sea_orm::DbConn; -use crate::{auth::User, loadenv::EnvVars}; +use crate::env::Vars; +#[allow(clippy::module_name_repetitions)] #[derive(Clone)] -pub(crate) struct AppState { - pub(crate) db: DatabaseConnection, - pub(crate) sitename: String, - pub(crate) base_host: String, - pub(crate) shady_host: String, - pub(crate) user: User, -} - -impl AppState { - pub(crate) fn new_from_env(db: DatabaseConnection, env: &EnvVars) -> Self { - let user = User { - id: 1, - username: env.username.clone(), - password_hash: Arc::new(env.password_hash.clone()), - }; - - Self { - db, - sitename: env.sitename.clone(), - base_host: env.base_host.clone(), - shady_host: env.shady_host.clone(), - user, - } - } +pub struct AppState { + pub(crate) db: Arc, + pub(crate) env: Vars, + pub(crate) protect: Arc, } diff --git a/src/templates.rs b/src/templates.rs deleted file mode 100644 index f0197f9..0000000 --- a/src/templates.rs +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: CC0-1.0 - * - * src/templates.rs - * - * This file is a component of ShadyURL by Elizabeth Myers. - * - * To the extent possible under law, the person who associated CC0 with - * ShadyURL has waived all copyright and related or neighboring rights - * to ShadyURL. - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ - -use askama::Template; - -use entity::url::Model; - -#[derive(Template)] -#[template(path = "index.html")] -pub(crate) struct IndexTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) sitename: &'a str, - pub(crate) auth_token: &'a str, -} - -#[derive(Template)] -#[template(path = "login_landing.html")] -pub(crate) struct LoginTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) err_str: &'a str, - pub(crate) sitename: &'a str, - pub(crate) auth_token: &'a str, -} - -#[derive(Template)] -#[template(path = "post.html")] -pub(crate) struct PostTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) shady_host: &'a str, - pub(crate) url: &'a str, - pub(crate) shady: &'a str, -} - -#[derive(Template)] -#[template(path = "post_error.html")] -pub(crate) struct PostErrorTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) url: &'a str, - pub(crate) reason: &'a str, -} - -#[derive(Template)] -#[template(path = "admin.html")] -pub(crate) struct AdminTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) sitename: &'a str, - pub(crate) urls: Vec, - pub(crate) auth_token: &'a str, -} - -#[derive(Template)] -#[template(path = "error.html")] -pub(crate) struct ErrorTemplate<'a> { - pub(crate) base_host: &'a str, - pub(crate) error_code: &'a str, - pub(crate) reason: &'a str, -} diff --git a/src/util.rs b/src/util.rs index 840fa65..75fa950 100644 --- a/src/util.rs +++ b/src/util.rs @@ -12,7 +12,8 @@ * work. If not, see . */ -pub(crate) mod macros { +pub mod macros { + #[macro_export] macro_rules! arr { ( $( #[$attr:meta] )* @@ -25,12 +26,3 @@ pub(crate) mod macros { pub(crate) use arr; } - -pub(crate) mod rng { - use rand::RngCore; - - #[inline] - pub(crate) fn default_rng() -> Box { - Box::new(rand::thread_rng()) - } -} diff --git a/src/validators.rs b/src/validators.rs index b46aabc..84dbb21 100644 --- a/src/validators.rs +++ b/src/validators.rs @@ -15,7 +15,7 @@ use url::{Host, Url}; use validator::ValidationError; -pub(crate) fn validate_url(url: &str) -> Result<(), ValidationError> { +pub fn validate_url(url: &str) -> Result<(), ValidationError> { if url.len() > 2047 { return Err(ValidationError::new("URL is too long")); } @@ -30,11 +30,13 @@ pub(crate) fn validate_url(url: &str) -> Result<(), ValidationError> { | "jabber" | "matrix" | "mumble" | "mxc" | "spotify" | "teamspeak" | "xmpp" => { match url_parsed .host() - .ok_or(ValidationError::new("No host found"))? + .ok_or_else(|| ValidationError::new("No host found"))? { Host::Ipv4(_) | Host::Ipv6(_) => Ok(()), Host::Domain(host_str) => { - let pos = host_str.rfind('.').ok_or(ValidationError::new("No TLD"))?; + let pos = host_str + .rfind('.') + .ok_or_else(|| ValidationError::new("No TLD"))?; if host_str.len() - pos < 2 { return Err(ValidationError::new("Invalid TLD")); } diff --git a/src/controllers/mod.rs b/src/web.rs similarity index 77% rename from src/controllers/mod.rs rename to src/web.rs index 490b996..a70e0ba 100644 --- a/src/controllers/mod.rs +++ b/src/web.rs @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: CC0-1.0 * - * src/controllers/mod.rs + * src/web.rs * * This file is a component of ShadyURL by Elizabeth Myers. * @@ -12,11 +12,11 @@ * work. If not, see . */ -mod admin; -pub(crate) use admin::*; - -mod err; -pub(crate) use err::*; +pub use app::App; -mod shadify; -pub(crate) use shadify::*; +mod admin; +mod app; +mod fallback; +mod files; +mod submission; +mod url; diff --git a/src/web/admin.rs b/src/web/admin.rs new file mode 100644 index 0000000..00d455b --- /dev/null +++ b/src/web/admin.rs @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/admin.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +pub mod auth; +pub mod delete; diff --git a/src/web/admin/auth.rs b/src/web/admin/auth.rs new file mode 100644 index 0000000..b974b82 --- /dev/null +++ b/src/web/admin/auth.rs @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/admin/auth.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use askama_axum::Template; +use axum::{ + extract::State, + response::{IntoResponse, Redirect, Response}, + routing::{get, post}, + Form, Router, +}; +use axum_messages::{Message, Messages}; +use csrf::CsrfProtection; +use tower_sessions::Session; +use tracing::debug; + +use crate::{ + auth::{AuthSession, Credentials}, + csrf as csrf_crate, + error_response::AppError, + state::AppState, +}; + +#[derive(Template)] +#[template(path = "admin/login.html")] +struct LoginTemplate { + authenticity_token: String, + messages: Vec, + sitename: String, +} + +pub fn router() -> Router { + Router::new() + .route("/login", post(self::post::login)) + .route("/login", get(self::get::login)) + .route("/logout", get(self::get::logout)) +} + +mod post { + use super::{ + csrf_crate, debug, AppError, AppState, AuthSession, Credentials, Form, IntoResponse, + Messages, Redirect, Response, Session, State, + }; + + pub(super) async fn login( + session: Session, + mut auth_session: AuthSession, + messages: Messages, + State(state): State, + Form(creds): Form, + ) -> Result { + csrf_crate::verify(&session, &creds.authenticity_token, &state.protect).await?; + + let Some(user) = auth_session.authenticate(creds.clone()).await? else { + debug!("Invalid credentials received"); + messages.error("Invalid credentials"); + return Ok(Redirect::to("/login").into_response()); + }; + + auth_session.login(&user).await?; + + debug!("Successful login, redirecting"); + + messages.success(format!("Successfully logged in as {}", user.0.username)); + + Ok(Redirect::to("/admin/urls").into_response()) + } +} + +mod get { + use super::{ + debug, AppError, AppState, AuthSession, CsrfProtection, IntoResponse, LoginTemplate, + Messages, Redirect, Response, Session, State, + }; + + pub(super) async fn login( + session: Session, + messages: Messages, + State(state): State, + ) -> Result { + let (authenticity_token, session_token) = state.protect.generate_token_pair(None, 300)?; + + let authenticity_token = authenticity_token.b64_string(); + let session_token = session_token.b64_string(); + + session.insert("authenticity_token", &session_token).await?; + + debug!("Token inserted"); + + Ok(LoginTemplate { + authenticity_token, + messages: messages.into_iter().collect(), + sitename: state.env.sitename.clone(), + } + .into_response()) + } + + pub(super) async fn logout( + messages: Messages, + mut auth_session: AuthSession, + ) -> Result { + auth_session.logout().await?; + messages.success("You have logged out"); + Ok(Redirect::to("/").into_response()) + } +} diff --git a/src/web/admin/delete.rs b/src/web/admin/delete.rs new file mode 100644 index 0000000..3d26016 --- /dev/null +++ b/src/web/admin/delete.rs @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/admin/delete.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use askama_axum::Template; +use axum::{ + extract::State, + response::{IntoResponse, Redirect, Response}, + routing::{get, post}, + Form, Router, +}; +use axum_messages::{Message, Messages}; +use csrf::CsrfProtection; +use serde::Deserialize; +use tower_sessions::Session; + +use entity::url; +use service::{Mutation, Query}; + +use crate::{auth::AuthSession, csrf as csrf_crate, error_response::AppError, state::AppState}; + +#[derive(Template)] +#[template(path = "admin/urls.html")] +struct UrlsTemplate<'a> { + authenticity_token: &'a str, + messages: Vec, + sitename: &'a str, + urls: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct DeleteForm { + authenticity_token: String, + id: i64, +} + +pub fn router() -> Router { + Router::new() + .route("/admin", get(|| async { Redirect::to("/admin/urls") })) + .route("/admin/urls", get(self::get::urls)) + .route("/admin/delete", post(self::post::delete)) +} + +mod post { + use super::{ + csrf_crate, AppError, AppState, AuthSession, DeleteForm, Form, IntoResponse, Messages, + Mutation, Redirect, Response, Session, State, + }; + + pub(super) async fn delete( + session: Session, + auth_session: AuthSession, + messages: Messages, + State(state): State, + Form(delete_form): Form, + ) -> Result { + csrf_crate::verify(&session, &delete_form.authenticity_token, &state.protect).await?; + + if auth_session.user.is_none() { + return Err(AppError::Unauthorized); + } + + Mutation::delete_url(&state.db, delete_form.id).await?; + + messages.success(format!("Deleted URL #{} successfully", delete_form.id)); + + Ok(Redirect::to("/admin/urls").into_response()) + } +} + +mod get { + use super::{ + AppError, AppState, AuthSession, CsrfProtection, IntoResponse, Messages, Query, Response, + Session, State, UrlsTemplate, + }; + + pub(super) async fn urls( + session: Session, + auth_session: AuthSession, + messages: Messages, + State(state): State, + ) -> Result { + if auth_session.user.is_none() { + return Err(AppError::Unauthorized); + } + + let (authenticity_token, session_token) = state.protect.generate_token_pair(None, 300)?; + + let authenticity_token = authenticity_token.b64_string(); + let session_token = session_token.b64_string(); + + session.insert("authenticity_token", &session_token).await?; + + let urls = Query::fetch_all_urls(&state.db).await?; + + Ok(UrlsTemplate { + authenticity_token: &authenticity_token, + messages: messages.into_iter().collect(), + sitename: &state.env.sitename, + urls, + } + .into_response()) + } +} diff --git a/src/web/app.rs b/src/web/app.rs new file mode 100644 index 0000000..24b4173 --- /dev/null +++ b/src/web/app.rs @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/app.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use std::{net::SocketAddr, sync::Arc}; + +use axum::Router; +use axum_login::AuthManagerLayerBuilder; +use axum_messages::MessagesManagerLayer; +use csrf::ChaCha20Poly1305CsrfProtection; +use sea_orm::{ConnectOptions, Database}; +use time::Duration; +use tokio::task::JoinHandle; +use tower::ServiceBuilder; +use tower_http::{normalize_path::NormalizePathLayer, timeout::TimeoutLayer}; +use tower_sessions::{Expiry, SessionManagerLayer}; +use tower_sessions_redis_store::{fred::prelude::*, RedisStore}; + +use migration::{Migrator, MigratorTrait}; + +use crate::{ + auth::Backend, + env::Vars, + state::AppState, + web::{ + admin::{auth, delete}, + fallback, files, submission, url, + }, +}; + +pub struct App { + state: AppState, + redis_pool: RedisPool, + redis_conn: JoinHandle>, +} + +impl App { + pub(crate) async fn new() -> Result> { + let env = Vars::load_env()?; + + let protect = ChaCha20Poly1305CsrfProtection::from_key(env.csrf_key); + + let redis_config = RedisConfig::from_url(&env.redis_url)?; + + let redis_pool = RedisPool::new(redis_config, None, None, None, env.redis_pool_size)?; + let redis_conn = redis_pool.connect(); + redis_pool.wait_for_connect().await?; + + let mut opt = ConnectOptions::new(&env.database_url); + opt.max_connections(100) + .min_connections(5) + .sqlx_logging(false); + + let db = Database::connect(opt).await?; + + Migrator::up(&db, None).await?; + + Ok(Self { + state: AppState { + db: Arc::new(db), + env, + protect: Arc::new(protect), + }, + redis_pool, + redis_conn, + }) + } + + pub(crate) async fn serve(self) -> Result<(), Box> { + let session_store = RedisStore::new(self.redis_pool); + let session_layer = SessionManagerLayer::new(session_store) + .with_domain( + self.state + .env + .base_host + .rsplit_once(':') + .map_or_else(|| self.state.env.base_host.clone(), |i| i.0.to_string()), + ) + .with_expiry(Expiry::OnInactivity(Duration::days(1))); + + let backend = Backend::new(self.state.db.clone()); + let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer.clone()).build(); + + let services = ServiceBuilder::new() + .layer(TimeoutLayer::new(Duration::seconds(15).unsigned_abs())) + .layer(NormalizePathLayer::trim_trailing_slash()) + .layer(session_layer) + .layer(MessagesManagerLayer) + .layer(auth_layer) + .layer(self.state.env.ip_source.clone().into_extension()); + + let app = Router::new() + .merge(auth::router()) + .merge(files::router()) + .merge(submission::router()) + .merge(delete::router()) + .merge(url::router()) + .merge(fallback::router()) + .layer(services) + .with_state(self.state); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await?; + + self.redis_conn.await??; + + Ok(()) + } +} diff --git a/src/web/fallback.rs b/src/web/fallback.rs new file mode 100644 index 0000000..992df1c --- /dev/null +++ b/src/web/fallback.rs @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/fallback.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use axum::{response::IntoResponse, Router}; + +use crate::{error_response::ErrorResponse, state::AppState}; + +pub fn router() -> Router { + Router::new().fallback(get::fallback) +} + +mod get { + use super::{ErrorResponse, IntoResponse}; + + pub(super) async fn fallback() -> impl IntoResponse { + ErrorResponse::not_found() + } +} diff --git a/src/web/files.rs b/src/web/files.rs new file mode 100644 index 0000000..8bcc3fe --- /dev/null +++ b/src/web/files.rs @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/files.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use axum::Router; +use tower_http::services::{ServeDir, ServeFile}; + +use crate::state::AppState; + +pub fn router() -> Router { + Router::new() + .nest_service("/robots.txt", ServeFile::new("static/robots.txt")) + .nest_service("/ads.txt", ServeFile::new("static/ads.txt")) + .nest_service("/app-ads.txt", ServeFile::new("static/app-ads.txt")) + .nest_service("/favicon.ico", ServeFile::new("static/favicon.ico")) + .nest_service("/assets", ServeDir::new("static/assets")) +} diff --git a/src/web/submission.rs b/src/web/submission.rs new file mode 100644 index 0000000..10e5567 --- /dev/null +++ b/src/web/submission.rs @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/submission.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use askama_axum::Template; +use axum::{ + debug_handler, + extract::State, + response::{IntoResponse, Response}, + routing::{get, post}, + Form, Router, +}; +use axum_client_ip::SecureClientIp; +use axum_messages::{Message, Messages}; +use serde::Deserialize; +use validator::Validate; + +use service::Mutation; + +use crate::{ + error_response::AppError, generate::Generator, state::AppState, validators::validate_url, +}; + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate<'a> { + messages: Vec, + base_host: &'a str, + sitename: &'a str, +} + +#[derive(Template)] +#[template(path = "submit.html")] +struct SubmissionTemplate<'a> { + messages: Vec, + shady_host: &'a str, + url: &'a str, + shady: &'a str, +} + +#[derive(Debug, Clone, Validate, Deserialize)] +struct UrlForm { + #[validate(custom(function = validate_url))] + pub(super) url: String, +} + +pub fn router() -> Router { + Router::new() + .route("/", get(self::get::index)) + .route("/submit", post(self::post::submit)) +} + +mod get { + use super::{AppState, IndexTemplate, IntoResponse, Messages, State}; + + pub(super) async fn index( + messages: Messages, + State(state): State, + ) -> impl IntoResponse { + IndexTemplate { + messages: messages.into_iter().collect(), + base_host: &state.env.base_host, + sitename: &state.env.sitename, + } + .into_response() + } +} + +mod post { + use super::{ + debug_handler, AppError, AppState, Form, Generator, IntoResponse, Messages, Mutation, + Response, SecureClientIp, State, SubmissionTemplate, UrlForm, Validate, + }; + + #[debug_handler] + pub(super) async fn submit( + messages: Messages, + SecureClientIp(addr): SecureClientIp, + State(state): State, + Form(url_form): Form, + ) -> Result { + if let Err(e) = url_form.validate() { + let error_reason = e + .field_errors() + .get("url") + .map_or("Unknown error".to_string(), |v| v[0].code.to_string()); + return Err(AppError::UrlValidation(error_reason, url_form.url)); + } + + let shady = Generator::shady_filename(); + + Mutation::create_url(&state.db, &url_form.url, &shady, Some(addr.to_string())).await?; + + Ok(SubmissionTemplate { + url: &url_form.url, + shady: &shady, + messages: messages.into_iter().collect(), + shady_host: &state.env.shady_host, + } + .into_response()) + } +} diff --git a/src/web/url.rs b/src/web/url.rs new file mode 100644 index 0000000..1fa093f --- /dev/null +++ b/src/web/url.rs @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: CC0-1.0 + * + * src/web/url.rs + * + * This file is a component of ShadyURL by Elizabeth Myers. + * + * To the extent possible under law, the person who associated CC0 with + * ShadyURL has waived all copyright and related or neighboring rights + * to ShadyURL. + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ + +use axum::{ + extract::{Path, State}, + response::{IntoResponse, Redirect, Response}, + routing::get, + Router, +}; +use itertools::join; + +use service::Query; + +use crate::{error_response::AppError, state::AppState}; + +pub fn router() -> Router { + Router::new() + .route("/reverse-map/*url", get(self::get::url)) + .route("/*shady", get(self::get::shady)) +} + +mod get { + use super::{join, AppError, AppState, IntoResponse, Path, Query, Redirect, Response, State}; + + pub(super) async fn url( + Path(url): Path, + State(state): State, + ) -> Result { + Ok(join( + Query::find_url_by_string(&state.db, &url) + .await? + .iter() + .map(|u| &u.shady), + "\n", + ) + .into_response()) + } + + pub(super) async fn shady( + Path(shady): Path, + State(state): State, + ) -> Result { + Query::find_url_by_shady_string(&state.db, &shady) + .await? + .map_or_else( + || Err(AppError::NotFound), + |url| Ok(Redirect::to(&url.url).into_response()), + ) + } +} diff --git a/static/assets/style.css b/static/assets/style.css index 3698443..027c939 100644 --- a/static/assets/style.css +++ b/static/assets/style.css @@ -142,18 +142,56 @@ div#header h2 { font-size: 2.00em; } -div#warning { - background-color: #FF0000; - color: #000000; - height: 1.50em; - align: center; - border: 1px solid #000000; +div#messages { margin: auto; width: 50%; + padding-top: 0.5em; + text-align: center; line-height: 1.50em; font-size: 1.25em; } +div#message-error { + background-color: #CC2936 !important; + color: #FFFFFF !important; + border: 1px solid #000000; + border-radius: 0.25em; + padding-bottom: 0.1em !important; +} + +div#message-warning { + background-color: #F58F29 !important; + color: #000000 !important; + border: 1px solid #000000; + border-radius: 0.25em; + padding-bottom: 0.1em !important; +} + +div#message-success { + background-color: #26A96C !important; + color: #000000 !important; + border: 1px solid #000000; + border-radius: 0.25em; + padding-bottom: 0.1em !important; +} + +div#message-info { + background-color: #2274A5 !important; + color: #FFFFFF !important; + border: 1px solid #000000; + border-radius: 0.25em; + padding-bottom: 0.1em !important; +} + +div#message-debug { + background-color: #FCF7F8 !important; + color: #000000 !important; + border: 1px solid #000000; + border-radius: 0.25em; + padding-bottom: 0.1em !important; +} + + div#content { margin: auto; line-height: 1.25em; diff --git a/templates/login_landing.html b/templates/admin/login.html similarity index 86% rename from templates/login_landing.html rename to templates/admin/login.html index ba7a072..3ef0464 100644 --- a/templates/login_landing.html +++ b/templates/admin/login.html @@ -1,6 +1,6 @@ {# SPDX-License-Identifier: CC0-1.0 # - # templates/login_landing.html + # templates/admin/login.html # # This file is a component of ShadyURL by Elizabeth Myers. # @@ -18,15 +18,12 @@ {% block header %}

{{ sitename }}

Admin login portal

-{% if err_str != "" %} -
{{ err_str }}
-{% endif %} {% endblock %} {% block content %}

- +





diff --git a/templates/admin.html b/templates/admin/urls.html similarity index 90% rename from templates/admin.html rename to templates/admin/urls.html index a03c56a..8623b2f 100644 --- a/templates/admin.html +++ b/templates/admin/urls.html @@ -1,6 +1,6 @@ {# SPDX-License-Identifier: CC0-1.0 # - # templates/admin.html + # templates/admin/urls.html # # This file is a component of ShadyURL by Elizabeth Myers. # @@ -32,8 +32,8 @@

Admin – {{ sitename }}

{% for entry in urls %} - - + +