Add initial setup and admin page
This commit is contained in:
482
Cargo.lock
generated
482
Cargo.lock
generated
@@ -220,7 +220,7 @@ version = "0.69.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
@@ -237,6 +237,12 @@ dependencies = [
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
@@ -264,6 +270,16 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
@@ -327,7 +343,29 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -422,6 +460,25 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
@@ -488,6 +545,12 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
|
||||
|
||||
[[package]]
|
||||
name = "devise"
|
||||
version = "0.4.2"
|
||||
@@ -514,7 +577,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
@@ -632,6 +695,18 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.1"
|
||||
@@ -676,6 +751,15 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@@ -854,6 +938,30 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpodder-rs"
|
||||
version = "0.1.0"
|
||||
@@ -861,10 +969,12 @@ dependencies = [
|
||||
"argon2",
|
||||
"base64",
|
||||
"chrono",
|
||||
"either",
|
||||
"lazy_static",
|
||||
"password-hash",
|
||||
"rocket",
|
||||
"rocket_db_pools",
|
||||
"rocket_dyn_templates",
|
||||
"semver",
|
||||
"sqlx",
|
||||
]
|
||||
@@ -1012,6 +1122,15 @@ 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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.7.0"
|
||||
@@ -1180,6 +1299,22 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
@@ -1197,6 +1332,26 @@ version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
@@ -1206,6 +1361,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
@@ -1221,7 +1385,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
@@ -1272,6 +1436,26 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -1315,7 +1499,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
@@ -1442,6 +1626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -1476,6 +1661,43 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "normpath"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.1"
|
||||
@@ -1608,6 +1830,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
@@ -1657,6 +1888,87 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -1885,7 +2197,7 @@ version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2040,6 +2352,17 @@ dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_dyn_templates"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"normpath",
|
||||
"notify",
|
||||
"rocket",
|
||||
"tera",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket_http"
|
||||
version = "0.6.0-dev"
|
||||
@@ -2098,7 +2421,7 @@ version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
@@ -2111,7 +2434,7 @@ version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
@@ -2306,6 +2629,15 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@@ -2448,6 +2780,16 @@ version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "slug"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
|
||||
dependencies = [
|
||||
"deunicode",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
@@ -2581,7 +2923,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
@@ -2623,7 +2965,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bitflags 2.9.4",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
@@ -2752,6 +3094,28 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"globwalk",
|
||||
"humansize",
|
||||
"lazy_static",
|
||||
"percent-encoding",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slug",
|
||||
"unic-segment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -3062,6 +3426,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
@@ -3072,6 +3442,56 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
||||
dependencies = [
|
||||
"unic-char-range",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-range"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-common"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-segment"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
|
||||
dependencies = [
|
||||
"unic-ucd-segment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-segment"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
|
||||
dependencies = [
|
||||
"unic-char-property",
|
||||
"unic-char-range",
|
||||
"unic-ucd-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-version"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
||||
dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -3163,6 +3583,16 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
@@ -3289,6 +3719,15 @@ dependencies = [
|
||||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
@@ -3306,7 +3745,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -3339,13 +3778,19 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3354,7 +3799,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3393,6 +3838,15 @@ dependencies = [
|
||||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||
dependencies = [
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
@@ -3430,7 +3884,7 @@ version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.3",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
|
||||
@@ -7,9 +7,11 @@ edition = "2024"
|
||||
base64 = "0.22.1"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
rocket = { path = "../rocket/core/lib", features = ["json", "tls", "secrets"] }
|
||||
rocket_dyn_templates = { path = "../rocket/contrib/dyn_templates", features = ["tera"] }
|
||||
rocket_db_pools = { path = "../rocket/contrib/db_pools/lib", features = ["sqlx_sqlite", "sqlx_macros"] }
|
||||
sqlx = "*"
|
||||
semver = { version = "1.0.26", features = ["serde"] }
|
||||
password-hash = { version = "0.5.0", features = ["std"] }
|
||||
argon2 = { version = "0.5.3", features = ["std"] }
|
||||
lazy_static = "1.5.0"
|
||||
either = "1.15.0"
|
||||
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM alpine
|
||||
WORKDIR /
|
||||
|
||||
COPY ./target/x86_64-unknown-linux-musl/release/gpodder-rs /gpodder-rs
|
||||
COPY ./Rocket.toml /Rocket.toml
|
||||
|
||||
VOLUME ["/data/"]
|
||||
CMD ["/gpodder-rs"]
|
||||
# ENTRYPOINT ["/bin/sh"]
|
||||
EXPOSE 8000/tcp
|
||||
10
Rocket.toml
10
Rocket.toml
@@ -1,14 +1,14 @@
|
||||
[default]
|
||||
log_level = "debug"
|
||||
log_level = "info"
|
||||
address = "0.0.0.0"
|
||||
port = 8000
|
||||
|
||||
[default.tls]
|
||||
key = "certs/key.pem"
|
||||
certs = "certs/cert.pem"
|
||||
# [default.tls]
|
||||
# key = "certs/key.pem"
|
||||
# certs = "certs/cert.pem"
|
||||
|
||||
[default.databases.podcast_db]
|
||||
url = "podcasts.sqlite"
|
||||
url = "data/podcasts.sqlite"
|
||||
|
||||
[default.limits]
|
||||
json = "100MiB"
|
||||
|
||||
173
src/admin.rs
Normal file
173
src/admin.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use argon2::Argon2;
|
||||
use either::Either;
|
||||
use password_hash::{PasswordHasher, SaltString, rand_core::OsRng};
|
||||
use rocket::{
|
||||
FromForm, Request, Route, TypedError, async_trait,
|
||||
form::Form,
|
||||
get,
|
||||
outcome::{Outcome, try_outcome},
|
||||
post,
|
||||
request::FromRequest,
|
||||
response::Redirect,
|
||||
routes,
|
||||
serde::{Deserialize, Serialize},
|
||||
trace::info,
|
||||
uri,
|
||||
};
|
||||
use rocket_db_pools::Connection;
|
||||
use rocket_dyn_templates::{Template, context};
|
||||
|
||||
use crate::{
|
||||
Db, SqlError,
|
||||
auth::{self, AdminUser, BasicAuth},
|
||||
};
|
||||
|
||||
#[get("/", rank = 1)]
|
||||
fn dash(auth: Option<BasicAuth>) -> Template {
|
||||
info!("user: {:?}", auth.as_ref().map(|a| a.username()));
|
||||
Template::render(
|
||||
"dash",
|
||||
context! {
|
||||
username: auth.as_ref().map(|a| a.username()),
|
||||
admin: auth.as_ref().map(|a| a.is_admin()).unwrap_or(false),
|
||||
mode: "dark",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
fn login(auth: Option<BasicAuth>) -> Either<Template, Redirect> {
|
||||
if auth.is_some() {
|
||||
Either::Right(Redirect::temporary(uri!(dash)))
|
||||
} else {
|
||||
Either::Left(Template::render(
|
||||
"login",
|
||||
context! {
|
||||
mode: "dark",
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/account")]
|
||||
fn account(auth: BasicAuth) -> Template {
|
||||
Template::render(
|
||||
"account",
|
||||
context! {
|
||||
username: auth.username(),
|
||||
admin: auth.is_admin(),
|
||||
mode: "dark",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[get("/admin")]
|
||||
async fn admin(auth: AdminUser, mut db: Connection<Db>) -> Result<Template, SqlError> {
|
||||
let users = sqlx::query!("SELECT * from users")
|
||||
.fetch_all(&mut **db)
|
||||
.await?;
|
||||
Ok(Template::render(
|
||||
"admin",
|
||||
context! {
|
||||
username: auth.username(),
|
||||
admin: auth.is_admin(),
|
||||
users: users.into_iter().filter(|r| r.name != auth.username()).map(|r| r.name).collect::<Vec<_>>(),
|
||||
mode: "dark",
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
struct InitialSetup;
|
||||
#[derive(Debug, TypedError)]
|
||||
pub enum InitialSetupError {
|
||||
#[error(status = 500)]
|
||||
InternalError,
|
||||
#[error(status = 500)]
|
||||
DbError(#[error(source)] SqlError),
|
||||
}
|
||||
impl From<SqlError> for InitialSetupError {
|
||||
fn from(value: SqlError) -> Self {
|
||||
Self::DbError(value)
|
||||
}
|
||||
}
|
||||
impl From<sqlx::Error> for InitialSetupError {
|
||||
fn from(value: sqlx::Error) -> Self {
|
||||
Self::DbError(value.into())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, TypedError)]
|
||||
struct Empty;
|
||||
impl From<Infallible> for Empty {
|
||||
fn from(value: Infallible) -> Self {
|
||||
match value {}
|
||||
}
|
||||
}
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for InitialSetup {
|
||||
type Forward = Empty;
|
||||
type Error = InitialSetupError;
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error, Self::Forward> {
|
||||
// TODO: cache result in managed state?
|
||||
let mut db = try_outcome!(
|
||||
req.guard::<Connection<Db>>()
|
||||
.await
|
||||
.map_error(|_| InitialSetupError::InternalError)
|
||||
);
|
||||
let user_count = try_outcome!(
|
||||
sqlx::query!("SELECT count(name) as count from users")
|
||||
.fetch_one(&mut **db)
|
||||
.await
|
||||
.into()
|
||||
);
|
||||
if user_count.count == 0 {
|
||||
Outcome::Success(Self)
|
||||
} else {
|
||||
Outcome::Forward(Empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn initial_setup(_initial: InitialSetup) -> Template {
|
||||
Template::render("initial_setup", context! {})
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromForm)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct InitialSetupOptions<'a> {
|
||||
admin_username: &'a str,
|
||||
admin_password: &'a str,
|
||||
}
|
||||
|
||||
#[post("/initial_setup", data = "<opts>")]
|
||||
async fn initial_complete(
|
||||
_initial: InitialSetup,
|
||||
opts: Form<InitialSetupOptions<'_>>,
|
||||
mut db: Connection<Db>,
|
||||
) -> Result<Redirect, SqlError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let pw_hash = Argon2::default()
|
||||
.hash_password(opts.admin_password.as_bytes(), salt.as_salt())
|
||||
.expect("Failed to hash password");
|
||||
sqlx::query("INSERT INTO users (name, role, password) VALUES (?1, \"admin\", ?2)")
|
||||
.bind(opts.admin_username)
|
||||
.bind(pw_hash.to_string())
|
||||
.execute(&mut **db)
|
||||
.await?;
|
||||
Ok(Redirect::to("/"))
|
||||
}
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![
|
||||
dash,
|
||||
login,
|
||||
auth::login_form,
|
||||
auth::update_password,
|
||||
account,
|
||||
admin,
|
||||
initial_setup,
|
||||
initial_complete,
|
||||
]
|
||||
}
|
||||
109
src/auth.rs
109
src/auth.rs
@@ -1,24 +1,19 @@
|
||||
#![allow(private_interfaces)]
|
||||
use std::str::Utf8Error;
|
||||
|
||||
use argon2::{Argon2, PasswordVerifier};
|
||||
use base64::{DecodeError, Engine, alphabet::STANDARD, engine::GeneralPurposeConfig};
|
||||
use password_hash::{PasswordHash, PasswordHasher, SaltString, rand_core::OsRng};
|
||||
use rocket::{
|
||||
Request, Route, TypedError, async_trait,
|
||||
http::{CookieJar, Status},
|
||||
post,
|
||||
request::{FromRequest, Outcome},
|
||||
routes,
|
||||
serde::{Deserialize, Serialize, json::Json},
|
||||
trace::debug,
|
||||
async_trait, catch, catchers, form::Form, http::{CookieJar, Status}, post, request::{FromRequest, Outcome}, response::Redirect, routes, serde::{Deserialize, Serialize}, trace::debug, uri, Catcher, FromForm, Request, Route, TypedError
|
||||
};
|
||||
use rocket_db_pools::Connection;
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
|
||||
use crate::{Db, SqlError};
|
||||
|
||||
pub struct BasicAuth {
|
||||
username: String,
|
||||
is_admin: bool,
|
||||
}
|
||||
#[derive(Debug, TypedError)]
|
||||
pub enum Unauthorized {
|
||||
@@ -33,6 +28,8 @@ pub enum Unauthorized {
|
||||
#[error(status = 401)]
|
||||
UserNotFound,
|
||||
#[error(status = 401)]
|
||||
UserNotAuthorized,
|
||||
#[error(status = 401)]
|
||||
PasswordIncorrect,
|
||||
#[error(status = 500)]
|
||||
InternalError,
|
||||
@@ -55,14 +52,21 @@ impl BasicAuth {
|
||||
async fn from_req<'r>(req: &'r Request<'_>) -> Result<Self, Unauthorized> {
|
||||
// TODO: actual sessions
|
||||
if let Some(cookie) = req.cookies().get_private("SESSION") {
|
||||
let raw = cookie.value();
|
||||
if let Some((role, username)) = raw.split_once(":") {
|
||||
return Ok(Self {
|
||||
username: cookie.value().into(),
|
||||
username: username.into(),
|
||||
is_admin: role == "admin"
|
||||
});
|
||||
// } else if let Some(username) = req.headers().get_one("test") {
|
||||
// return Ok(Self {
|
||||
// username: username.into(),
|
||||
// });
|
||||
} else {
|
||||
return Err(Unauthorized::InternalError);
|
||||
}
|
||||
}
|
||||
// else if let Some(username) = req.headers().get_one("test") {
|
||||
// return Ok(Self {
|
||||
// username: username.into()
|
||||
// })
|
||||
// }
|
||||
let auth = req
|
||||
.headers()
|
||||
.get_one("Authorization")
|
||||
@@ -89,14 +93,13 @@ impl BasicAuth {
|
||||
.ok_or(Unauthorized::UserNotFound)?;
|
||||
let hashed =
|
||||
PasswordHash::new(&user.password).expect("Invalid password hash stored in the db");
|
||||
// hashed
|
||||
// .verify_password(&[&Argon2::default()], pass)
|
||||
Argon2::default().verify_password(pass.as_bytes(), &hashed)
|
||||
.map_err(|_| Unauthorized::PasswordIncorrect)
|
||||
.map(|()| {
|
||||
req.cookies().add_private(("SESSION", user.name.clone()));
|
||||
req.cookies().add_private(("SESSION", format!("{}:{}", user.role, user.name)));
|
||||
Self {
|
||||
username: user.name,
|
||||
is_admin: user.role == "admin"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -118,6 +121,35 @@ impl BasicAuth {
|
||||
pub fn username(&self) -> &str {
|
||||
&self.username
|
||||
}
|
||||
// pub fn into_username(self) -> String {
|
||||
// self.username
|
||||
// }
|
||||
|
||||
pub fn is_admin(&self) -> bool {
|
||||
self.is_admin
|
||||
}}
|
||||
|
||||
pub struct AdminUser(BasicAuth);
|
||||
|
||||
impl std::ops::Deref for AdminUser {
|
||||
type Target = BasicAuth;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'r> FromRequest<'r> for AdminUser {
|
||||
type Forward = std::convert::Infallible;
|
||||
type Error = Unauthorized;
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error, Self::Forward> {
|
||||
match BasicAuth::from_req(req).await {
|
||||
Ok(v) if v.is_admin => rocket::outcome::Outcome::Success(Self(v)),
|
||||
Ok(_) => rocket::outcome::Outcome::Error(Unauthorized::UserNotAuthorized),
|
||||
Err(v) => rocket::outcome::Outcome::Error(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/auth/<username>/login.json")]
|
||||
@@ -129,23 +161,22 @@ pub fn login(username: &str, auth: BasicAuth) -> Result<&'static str, Status> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, FromForm)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct PasswordChange<'a> {
|
||||
password: &'a str,
|
||||
}
|
||||
|
||||
#[post("/auth/update_password", data = "<pw>")]
|
||||
#[post("/update_password", data = "<pw>")]
|
||||
pub async fn update_password(
|
||||
auth: BasicAuth,
|
||||
pw: Json<PasswordChange<'_>>,
|
||||
pw: Form<PasswordChange<'_>>,
|
||||
mut db: Connection<Db>,
|
||||
) -> Result<&'static str, SqlError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let pw_hash = Argon2::default()
|
||||
.hash_password(pw.password.as_bytes(), salt.as_salt())
|
||||
.expect("Failed to hash password");
|
||||
// pw_hash.to_string()
|
||||
sqlx::query("INSERT INTO users (name, password) VALUES (?1, ?2) ON CONFLICT DO UPDATE SET password = ?2")
|
||||
.bind(auth.username)
|
||||
.bind(pw_hash.to_string())
|
||||
@@ -154,6 +185,38 @@ pub async fn update_password(
|
||||
Ok("")
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromForm)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct LoginForm<'a> {
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
}
|
||||
|
||||
#[post("/login", data = "<pw>")]
|
||||
pub async fn login_form(
|
||||
pw: Form<LoginForm<'_>>,
|
||||
mut db: Connection<Db>,
|
||||
cookies: &CookieJar<'_>,
|
||||
) -> Result<Redirect, Unauthorized> {
|
||||
let user = sqlx::query!("SELECT * from users where name = ?", pw.username)
|
||||
.fetch_optional(&mut **db)
|
||||
.await?
|
||||
.ok_or(Unauthorized::UserNotFound)?;
|
||||
let hashed =
|
||||
PasswordHash::new(&user.password).expect("Invalid password hash stored in the db");
|
||||
Argon2::default().verify_password(pw.password.as_bytes(), &hashed)
|
||||
.map_err(|_| Unauthorized::PasswordIncorrect)?;
|
||||
cookies.add_private(("SESSION", format!("{}:{}", user.role, pw.username)));
|
||||
Ok(Redirect::to(uri!("/")))
|
||||
}
|
||||
|
||||
#[catch(401, error="<e>")]
|
||||
pub fn auth_failure(e: &Unauthorized) -> Template {
|
||||
Template::render("401", context! {
|
||||
error: format!("{e:?}")
|
||||
})
|
||||
}
|
||||
|
||||
#[post("/auth/<username>/logout.json")]
|
||||
pub fn logout(
|
||||
username: &str,
|
||||
@@ -169,5 +232,9 @@ pub fn logout(
|
||||
}
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![login, logout, update_password]
|
||||
routes![login, logout]
|
||||
}
|
||||
|
||||
pub fn catchers() -> Vec<Catcher> {
|
||||
catchers![auth_failure]
|
||||
}
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -1,3 +1,4 @@
|
||||
#![allow(private_interfaces)]
|
||||
use rocket::{catch, catchers, fairing::AdHoc, launch, TypedError};
|
||||
|
||||
mod auth;
|
||||
@@ -7,14 +8,18 @@ mod subscriptions;
|
||||
mod suggestions;
|
||||
mod time;
|
||||
mod episodes;
|
||||
mod admin;
|
||||
use rocket_db_pools::{
|
||||
Database,
|
||||
sqlx,
|
||||
};
|
||||
use rocket_dyn_templates::Template;
|
||||
pub use time::Timestamp;
|
||||
mod format;
|
||||
pub use format::*;
|
||||
|
||||
use crate::auth::catchers;
|
||||
|
||||
#[derive(Debug, TypedError)]
|
||||
pub struct SqlError(sqlx::Error);
|
||||
|
||||
@@ -34,8 +39,8 @@ fn catch_sql(error: &SqlError) -> String {
|
||||
struct Db(sqlx::SqlitePool);
|
||||
|
||||
const SQL_INIT: &[&str] = &[
|
||||
"CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY NOT NULL, password TEXT NOT NULL);",
|
||||
"REPLACE INTO users (name, password) VALUES ('matt', 'pass')",
|
||||
"CREATE TABLE IF NOT EXISTS users (name TEXT PRIMARY KEY NOT NULL, role TEXT NOT NULL, password TEXT NOT NULL);",
|
||||
// "REPLACE INTO users (name, password) VALUES ('matt', 'admin', 'pass')",
|
||||
"CREATE TABLE IF NOT EXISTS devices (
|
||||
id TEXT NOT NULL,
|
||||
user TEXT NOT NULL,
|
||||
@@ -77,6 +82,7 @@ const SQL_INIT: &[&str] = &[
|
||||
fn launch() -> _ {
|
||||
rocket::build()
|
||||
.attach(Db::init())
|
||||
.attach(Template::fairing())
|
||||
.attach(AdHoc::on_liftoff("Init db", |r| {
|
||||
Box::pin(async {
|
||||
if let Some(db) = Db::fetch(r) {
|
||||
@@ -98,5 +104,7 @@ fn launch() -> _ {
|
||||
// .mount("/", suggestions::routes())
|
||||
// .mount("/api/2", suggestions::routes())
|
||||
.mount("/api/2", episodes::routes())
|
||||
.mount("/", admin::routes())
|
||||
.register("/", catchers![catch_sql])
|
||||
.register("/", catchers())
|
||||
}
|
||||
|
||||
7
templates/401.html.tera
Normal file
7
templates/401.html.tera
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="m-3">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
17
templates/_basic.html.tera
Normal file
17
templates/_basic.html.tera
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% block head %}
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
<div id="header">{% block header %}{% endblock header %}</div>
|
||||
<div id="content">{% block content %}{% endblock content %}</div>
|
||||
<div id="footer">{% block footer %}{% endblock footer %}</div>
|
||||
{% endblock body %}
|
||||
</body>
|
||||
</html>
|
||||
48
templates/_layout.html.tera
Normal file
48
templates/_layout.html.tera
Normal file
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
{% block head %}
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
{#<link href="style.css" rel="stylesheet" />#}
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
<div id="header">
|
||||
{% block header %}
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">GPodder-rs</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
{% if username is defined and username is string %}
|
||||
{% if admin %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin">Admin</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/account">Account ({{username}})</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/login">Login</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endblock header %}
|
||||
</div>
|
||||
<div id="content">{% block content %}{% endblock %}</div>
|
||||
<div id="footer">{% block footer %}{% endblock %}</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
{% endblock body %}
|
||||
</body>
|
||||
</html>
|
||||
15
templates/account.html.tera
Normal file
15
templates/account.html.tera
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
{% block content %}
|
||||
<form class="p-4 d-flex container-fluid" method="POST" action="/update_password">
|
||||
<div>
|
||||
<label for="pass" class="form-label">Update Password</label>
|
||||
</div>
|
||||
<div class="flex-grow-1 mx-4">
|
||||
<input type="password" class="form-control" id="pass" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Update Password</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
49
templates/admin.html.tera
Normal file
49
templates/admin.html.tera
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="vstack gap-3 m-3">
|
||||
<h3>Create new user</h3>
|
||||
<form method="POST" action="/create_user">
|
||||
<div class="mb-3">
|
||||
<label for="user" class="form-label">Username</label>
|
||||
<input type="username" class="form-control" id="user" name="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pass" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="pass" name="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Role</label>
|
||||
<select class="form-select" aria-label="User Role" name="role" id="role">
|
||||
<option selected value="user">User</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create new user</button>
|
||||
</form>
|
||||
<hr />
|
||||
<h3>Edit user</h3>
|
||||
<form method="POST" action="/edit_user">
|
||||
<div class="mb-3">
|
||||
<label for="user" class="form-label">Username</label>
|
||||
<select class="form-select" aria-label="User to edit" name="username" id="user">
|
||||
{% for user in users %}
|
||||
<option value="{{ user }}">{{ user }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pass" class="form-label">Password (leave blank to skip changing)</label>
|
||||
<input type="password" class="form-control" id="pass" name="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label">Role</label>
|
||||
<select class="form-select" aria-label="User Role" name="role" id="role">
|
||||
<option selected value="user">User</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create new user</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
2
templates/dash.html.tera
Normal file
2
templates/dash.html.tera
Normal file
@@ -0,0 +1,2 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
15
templates/initial_setup.html.tera
Normal file
15
templates/initial_setup.html.tera
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
{% block content %}
|
||||
<form class="m-3" method="POST" action="/initial_setup">
|
||||
<div class="mb-3">
|
||||
<label for="user" class="form-label">Admin Username</label>
|
||||
<input type="username" class="form-control" id="user" name="admin_username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pass" class="form-label">Admin Password</label>
|
||||
<input type="password" class="form-control" id="pass" name="admin_password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Complete initial setup</button>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
15
templates/login.html.tera
Normal file
15
templates/login.html.tera
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "_layout" %}
|
||||
|
||||
{% block content %}
|
||||
<form class="m-3" method="POST" action="/login">
|
||||
<div class="mb-3">
|
||||
<label for="user" class="form-label">Username</label>
|
||||
<input type="username" class="form-control" id="user" name="username">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="pass" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="pass" name="password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
Reference in New Issue
Block a user