diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6c343c0..6a653d1 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -7,3 +7,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/rust:0-${VARIANT} RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install clang lld \ && apt-get autoremove -y && apt-get clean -y + +# Add tools for wasm development +RUN rustup target add wasm32-unknown-unknown \ + && cargo install trunk cargo-edit cargo-watch \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2303100..6d8c150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -34,6 +69,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -155,9 +199,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -227,6 +271,32 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum_database_sessions" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7451dbb80a9bff3f227e8c54e3aa37b9face1dc19e034ae2a40c87f764a2ffb5" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "chrono", + "cookie", + "dashmap", + "futures", + "http", + "http-body", + "serde", + "serde_json", + "sqlx", + "thiserror", + "tokio", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + [[package]] name = "base64" version = "0.13.0" @@ -316,9 +386,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -340,23 +410,67 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", + "time 0.1.44", + "wasm-bindgen", "winapi", ] +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[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.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +dependencies = [ + "aes-gcm", + "base64", + "percent-encoding", + "rand", + "subtle", + "time 0.3.15", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -411,11 +525,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -439,6 +554,72 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cxx" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.3", +] + [[package]] name = "digest" version = "0.9.0" @@ -536,10 +717,25 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.21" +name = "futures" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -547,9 +743,20 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-intrusive" @@ -564,30 +771,43 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -615,10 +835,20 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "h2" version = "0.3.11" @@ -822,6 +1052,30 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" version = "0.2.3" @@ -843,6 +1097,15 @@ dependencies = [ "hashbrown 0.11.2", ] +[[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" @@ -890,6 +1153,7 @@ dependencies = [ "askama", "async-session", "axum", + "axum_database_sessions", "headers", "http", "oauth2", @@ -921,24 +1185,34 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -1012,24 +1286,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] @@ -1043,15 +1307,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-integer" version = "0.1.44" @@ -1081,6 +1336,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "oauth2" version = "4.1.0" @@ -1103,9 +1367,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1131,7 +1395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.3", ] [[package]] @@ -1150,9 +1414,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1195,9 +1459,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1205,6 +1469,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1213,11 +1489,11 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1398,6 +1674,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "sct" version = "0.7.0" @@ -1410,18 +1692,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -1430,9 +1712,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", @@ -1623,6 +1905,7 @@ dependencies = [ "thiserror", "tokio-stream", "url", + "uuid", "webpki-roots", "whoami", ] @@ -1676,13 +1959,13 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1692,19 +1975,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" [[package]] -name = "thiserror" -version = "1.0.30" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1720,6 +2012,35 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tinyvec" version = "1.5.1" @@ -1737,16 +2058,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", - "once_cell", "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", @@ -1879,15 +2200,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", @@ -1898,9 +2219,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1909,11 +2230,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -1973,6 +2294,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -1989,10 +2316,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-width" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode_categories" @@ -2000,6 +2327,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -2053,9 +2390,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" @@ -2179,6 +2522,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2187,9 +2539,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -2200,33 +2552,33 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index 337a4c3..0586555 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] axum = { version = "0.5.16", features = ["headers"] } +axum_database_sessions = { version = "4.1.0", features = [ "postgres-rustls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.68" tokio = { version = "1.0", features = ["full"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7609593 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +// generated by `sqlx migrate build-script` +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} \ No newline at end of file diff --git a/migrations/20221012234646_initial_setup.sql b/migrations/20221012234646_initial_setup.sql new file mode 100644 index 0000000..90f3313 --- /dev/null +++ b/migrations/20221012234646_initial_setup.sql @@ -0,0 +1,12 @@ +-- Add migration script here +CREATE TABLE IF NOT EXISTS accounts ( + user_id serial primary key, + created_on timestamp not null, + last_login timestamp, + last_name varchar(50) not null, + first_name varchar(50) not null, + discord_id varchar(50), + discord_avatar varchar(255), + discord_username varchar(50), + discord_discriminator varchar(50) +); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 394c6c4..559eb25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,275 +1,13 @@ +use std::net::SocketAddr; use askama::Template; -use async_session::{MemoryStore, Session, SessionStore as _}; +use axum_database_sessions::{AxumSession, AxumPgPool, AxumSessionConfig, AxumSessionStore, AxumSessionLayer}; use axum::{ - async_trait, - extract::{ - rejection::TypedHeaderRejectionReason, Extension, FromRequest, RequestParts, - TypedHeader, - }, - headers::Cookie, - http::{ - self, - header::{HeaderValue}, - StatusCode - }, - response::{Html, IntoResponse, Redirect, Response}, - routing::{get, get_service}, Router, + routing::{get, get_service}, response::{Html, IntoResponse, Response} }; -use http::{header}; -use oauth2::{ - basic::BasicClient, -}; -use serde::{Deserialize, Serialize}; -use std::{net::SocketAddr, collections::HashMap}; +use http::StatusCode; +use serde::{Serialize, Deserialize}; use tower_http::services::ServeDir; -use uuid::Uuid; - -use sqlx::{PgPool}; -use anyhow::*; -use sqlx::postgres::PgPoolOptions; - -mod db; - -use db::*; - -mod google_oauth; -mod facebook_oauth; -mod discord_oauth; - -use google_oauth::*; -use facebook_oauth::*; -use discord_oauth::*; - -const COOKIE_NAME: &str = "SESSION"; - -// The user data we'll get back from Discord. -// https://discord.com/developers/docs/resources/user#user-object-user-structure -#[derive(Debug, Serialize, Deserialize)] -struct User { - id: String, - avatar: Option, - username: String, - discriminator: String, -} - -#[tokio::main] -async fn main() { - // Set the default environment variables - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "example_sessions=debug") - } - if std::env::var_os("GOOGLE_CLIENT_ID").is_none() { - std::env::set_var("GOOGLE_CLIENT_ID", "735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com") - } - if std::env::var_os("GOOGLE_CLIENT_SECRET").is_none() { - std::env::set_var("GOOGLE_CLIENT_SECRET", "L6uI7FQGoMJd-ay1HO_iGJ6M") - } - if std::env::var_os("DISCORD_CLIENT_ID").is_none() { - std::env::set_var("DISCORD_CLIENT_ID", "956189108559036427") - } - if std::env::var_os("DISCORD_CLIENT_SECRET").is_none() { - std::env::set_var("DISCORD_CLIENT_SECRET", "dx2DZxjDhVMCCnGX4xpz5MxSTgZ4lHBI") - } - if std::env::var_os("FACEBOOK_CLIENT_ID").is_none() { - std::env::set_var("FACEBOOK_CLIENT_ID", "1529124327484248") - } - if std::env::var_os("FACEBOOK_CLIENT_SECRET").is_none() { - std::env::set_var("FACEBOOK_CLIENT_SECRET", "189509b5eb907b3ce34b7e8459030f21") - } - - // initialize tracing - tracing_subscriber::fmt::init(); - - // Initialize database - let db = DBApplication::new("postgres://postgres:postgres@localhost/sqlx-demo".into()).await?; - println!("Connection acquired!"); - - - // `MemoryStore` just used as an example. Don't use this in production. - let store = MemoryStore::new(); - - // Create HashMap to store oauth configurations - let mut oauth_clients = HashMap::<&str, BasicClient>::new(); - - // Get the client structures - let facebook_oauth_client = facebook_oauth_client(); - let discord_oauth_client = discord_oauth_client(); - let google_oauth_client = google_oauth_client(); - - // Get oauth clients for the hashmap - oauth_clients.insert("Facebook", facebook_oauth_client); - oauth_clients.insert("Discord", discord_oauth_client); - oauth_clients.insert("Google", google_oauth_client); - - // build our application with a route - let app = Router::new() - // `GET /` goes to `root` - .nest( - "/assets", - get_service(ServeDir::new("templates/assets")).handle_error( - |error: std::io::Error| async move { - ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Unhandled internal error: {}", error), - ) - }, - ), - ) - .route("/", get(index)) - .route("/login", get(login)) - .route("/logout", get(logout)) - .route("/dashboard", get(dashboard)) - .route("/google_auth", get(google_auth)) - .route("/auth/google", get(google_authorized)) - .route("/facebook_auth", get(facebook_auth)) - .route("/auth/facebook", get(facebook_authorized)) - .route("/discord_auth", get(discord_auth)) - .route("/auth/discord", get(discord_authorized)) - .layer(Extension(store)) - .layer(Extension(oauth_clients)); - - // run our app with hyper - // `axum::Server` is a re-export of `hyper::Server` - let addr = SocketAddr::from(([0, 0, 0, 0], 40192)); - tracing::debug!("listening on {}", addr); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); -} - -// Session is optional -async fn index(user: Option) -> impl IntoResponse { - let (userid, name) = match user { - Some(u) => (true, u.username), - None => (false, "You're not logged in.".to_string()), - }; - let template = IndexTemplate { userid, name }; - HtmlTemplate(template) -} - -async fn login() -> impl IntoResponse { - let name = "".to_string(); - let userid = false; - let template = LoginTemplate { userid, name }; - HtmlTemplate(template) -} - -// Valid user session required. If there is none, redirect to the auth page -async fn dashboard(user: User) -> impl IntoResponse { - let name = user.username; - let userid = true; - let template = DashboardTemplate { userid, name }; - HtmlTemplate(template) -} - -#[derive(Template)] -#[template(path = "login.html")] -struct LoginTemplate { - userid: bool, - name: String, -} - -#[derive(Template)] -#[template(path = "dashboard.html")] -struct DashboardTemplate { - userid: bool, - name: String, -} - -struct FreshUserId { - pub user_id: UserId, - pub cookie: HeaderValue, -} - -enum UserIdFromSession { - FoundUserId(UserId), - CreatedFreshUserId(FreshUserId), -} - -#[async_trait] -impl FromRequest for UserIdFromSession -where - B: Send, -{ - type Rejection = (StatusCode, &'static str); - - async fn from_request(req: &mut RequestParts) -> Result { - let Extension(store) = Extension::::from_request(req) - .await - .expect("`MemoryStore` extension missing"); - - let cookie = Option::>::from_request(req) - .await - .unwrap(); - - let session_cookie = cookie.as_ref().and_then(|cookie| cookie.get(COOKIE_NAME)); - - // return the new created session cookie for client - if session_cookie.is_none() { - let user_id = UserId::new(); - let mut session = Session::new(); - session.insert("user_id", user_id).unwrap(); - let cookie = store.store_session(session).await.unwrap().unwrap(); - return Ok(Self::CreatedFreshUserId(FreshUserId { - user_id, - cookie: HeaderValue::from_str(format!("{}={}", COOKIE_NAME, cookie).as_str()) - .unwrap(), - })); - } - - tracing::debug!( - "UserIdFromSession: got session cookie from user agent, {}={}", - COOKIE_NAME, - session_cookie.unwrap() - ); - // continue to decode the session cookie - let user_id = if let Some(session) = store - .load_session(session_cookie.unwrap().to_owned()) - .await - .unwrap() - { - if let Some(user_id) = session.get::("user_id") { - tracing::debug!( - "UserIdFromSession: session decoded success, user_id={:?}", - user_id - ); - user_id - } else { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - "No `user_id` found in session", - )); - } - } else { - tracing::debug!( - "UserIdFromSession: err session not exists in store, {}={}", - COOKIE_NAME, - session_cookie.unwrap() - ); - return Err((StatusCode::BAD_REQUEST, "No session found for cookie")); - }; - - Ok(Self::FoundUserId(user_id)) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -struct UserId(Uuid); - -impl UserId { - fn new() -> Self { - Self(Uuid::new_v4()) - } -} - -#[derive(Template)] -#[template(path = "index.html")] -struct IndexTemplate { - userid: bool, - name: String, -} struct HtmlTemplate(T); @@ -289,62 +27,91 @@ where } } -async fn logout( - Extension(store): Extension, - TypedHeader(cookies): TypedHeader, -) -> impl IntoResponse { - let cookie = cookies.get(COOKIE_NAME).unwrap(); - let session = match store.load_session(cookie.to_string()).await.unwrap() { - Some(s) => s, - // No session active, just redirect - None => return Redirect::to(&"/"), - }; - - store.destroy_session(session).await.unwrap(); - - Redirect::to(&"/") +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate { + logged_in: bool, + name: String, } -struct AuthRedirect; - -impl IntoResponse for AuthRedirect { - fn into_response(self) -> Response { - Redirect::temporary(&"/login").into_response() - } +#[derive(Template)] +#[template(path = "login.html")] +struct LoginTemplate { + logged_in: bool, + name: String, } -#[async_trait] -impl FromRequest for User -where - B: Send, -{ - // If anything goes wrong or no session is found, redirect to the auth page - type Rejection = AuthRedirect; +#[derive(Debug, Serialize, Deserialize)] +struct User { + id: String, + avatar: Option, + username: String, + discriminator: String, +} - async fn from_request(req: &mut RequestParts) -> Result { - let Extension(store) = Extension::::from_request(req) - .await - .expect("`MemoryStore` extension is missing"); +#[tokio::main] +async fn main() { + // initialize tracing + tracing_subscriber::fmt::init(); - let cookies = TypedHeader::::from_request(req) - .await - .map_err(|e| match *e.name() { - header::COOKIE => match e.reason() { - TypedHeaderRejectionReason::Missing => AuthRedirect, - _ => panic!("unexpected error getting Cookie header(s): {}", e), + let session_config = AxumSessionConfig::default() + .with_table_name("sessions"); + + let session_store = AxumSessionStore::::new(None, session_config); + session_store.initiate().await.unwrap(); + + // build our application with some routes + let app = Router::new() + .nest( + "/assets", + get_service(ServeDir::new("templates/assets")).handle_error( + |error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) }, - _ => panic!("unexpected error getting cookies: {}", e), - })?; - let session_cookie = cookies.get(COOKIE_NAME).ok_or(AuthRedirect)?; + ), + ) + .route("/", get(index)) + .route("/login", get(login)) + .layer(AxumSessionLayer::new(session_store)); - let session = store - .load_session(session_cookie.to_string()) - .await - .unwrap() - .ok_or(AuthRedirect)?; - - let user = session.get::("user").ok_or(AuthRedirect)?; - - Ok(user) - } + // run it + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); } + +async fn index(session: AxumSession) -> impl IntoResponse { + let logged_in = session.get("logged_in").await.unwrap_or(false); + let name = session.get("name").await.unwrap_or("You're not logged in.".to_string()); + + let template = IndexTemplate { logged_in, name}; + HtmlTemplate(template) +} + +async fn login(session: AxumSession) -> impl IntoResponse { + let logged_in = session.get("logged_in").await.unwrap_or(false); + let name = session.get("name").await.unwrap_or("".to_string()); + + session.set_store(true).await; + + let template = LoginTemplate { logged_in, name }; + HtmlTemplate(template) +} + +//No need to set the sessions accepted or not with gdpr mode disabled +async fn greet(session: AxumSession) -> String { + let mut count: usize = session.get("count").await.unwrap_or(0); + + // Allow the Session data to be keep in memory and the database for the lifetime. + session.set_store(true).await; + count += 1; + session.set("count", count).await; + + count.to_string() +} \ No newline at end of file diff --git a/src/oldmain.rs b/src/oldmain.rs new file mode 100644 index 0000000..394c6c4 --- /dev/null +++ b/src/oldmain.rs @@ -0,0 +1,350 @@ +use askama::Template; +use async_session::{MemoryStore, Session, SessionStore as _}; +use axum::{ + async_trait, + extract::{ + rejection::TypedHeaderRejectionReason, Extension, FromRequest, RequestParts, + TypedHeader, + }, + headers::Cookie, + http::{ + self, + header::{HeaderValue}, + StatusCode + }, + response::{Html, IntoResponse, Redirect, Response}, + routing::{get, get_service}, + Router, +}; +use http::{header}; +use oauth2::{ + basic::BasicClient, +}; +use serde::{Deserialize, Serialize}; +use std::{net::SocketAddr, collections::HashMap}; +use tower_http::services::ServeDir; +use uuid::Uuid; + +use sqlx::{PgPool}; +use anyhow::*; +use sqlx::postgres::PgPoolOptions; + +mod db; + +use db::*; + +mod google_oauth; +mod facebook_oauth; +mod discord_oauth; + +use google_oauth::*; +use facebook_oauth::*; +use discord_oauth::*; + +const COOKIE_NAME: &str = "SESSION"; + +// The user data we'll get back from Discord. +// https://discord.com/developers/docs/resources/user#user-object-user-structure +#[derive(Debug, Serialize, Deserialize)] +struct User { + id: String, + avatar: Option, + username: String, + discriminator: String, +} + +#[tokio::main] +async fn main() { + // Set the default environment variables + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_sessions=debug") + } + if std::env::var_os("GOOGLE_CLIENT_ID").is_none() { + std::env::set_var("GOOGLE_CLIENT_ID", "735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com") + } + if std::env::var_os("GOOGLE_CLIENT_SECRET").is_none() { + std::env::set_var("GOOGLE_CLIENT_SECRET", "L6uI7FQGoMJd-ay1HO_iGJ6M") + } + if std::env::var_os("DISCORD_CLIENT_ID").is_none() { + std::env::set_var("DISCORD_CLIENT_ID", "956189108559036427") + } + if std::env::var_os("DISCORD_CLIENT_SECRET").is_none() { + std::env::set_var("DISCORD_CLIENT_SECRET", "dx2DZxjDhVMCCnGX4xpz5MxSTgZ4lHBI") + } + if std::env::var_os("FACEBOOK_CLIENT_ID").is_none() { + std::env::set_var("FACEBOOK_CLIENT_ID", "1529124327484248") + } + if std::env::var_os("FACEBOOK_CLIENT_SECRET").is_none() { + std::env::set_var("FACEBOOK_CLIENT_SECRET", "189509b5eb907b3ce34b7e8459030f21") + } + + // initialize tracing + tracing_subscriber::fmt::init(); + + // Initialize database + let db = DBApplication::new("postgres://postgres:postgres@localhost/sqlx-demo".into()).await?; + println!("Connection acquired!"); + + + // `MemoryStore` just used as an example. Don't use this in production. + let store = MemoryStore::new(); + + // Create HashMap to store oauth configurations + let mut oauth_clients = HashMap::<&str, BasicClient>::new(); + + // Get the client structures + let facebook_oauth_client = facebook_oauth_client(); + let discord_oauth_client = discord_oauth_client(); + let google_oauth_client = google_oauth_client(); + + // Get oauth clients for the hashmap + oauth_clients.insert("Facebook", facebook_oauth_client); + oauth_clients.insert("Discord", discord_oauth_client); + oauth_clients.insert("Google", google_oauth_client); + + // build our application with a route + let app = Router::new() + // `GET /` goes to `root` + .nest( + "/assets", + get_service(ServeDir::new("templates/assets")).handle_error( + |error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }, + ), + ) + .route("/", get(index)) + .route("/login", get(login)) + .route("/logout", get(logout)) + .route("/dashboard", get(dashboard)) + .route("/google_auth", get(google_auth)) + .route("/auth/google", get(google_authorized)) + .route("/facebook_auth", get(facebook_auth)) + .route("/auth/facebook", get(facebook_authorized)) + .route("/discord_auth", get(discord_auth)) + .route("/auth/discord", get(discord_authorized)) + .layer(Extension(store)) + .layer(Extension(oauth_clients)); + + // run our app with hyper + // `axum::Server` is a re-export of `hyper::Server` + let addr = SocketAddr::from(([0, 0, 0, 0], 40192)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +// Session is optional +async fn index(user: Option) -> impl IntoResponse { + let (userid, name) = match user { + Some(u) => (true, u.username), + None => (false, "You're not logged in.".to_string()), + }; + let template = IndexTemplate { userid, name }; + HtmlTemplate(template) +} + +async fn login() -> impl IntoResponse { + let name = "".to_string(); + let userid = false; + let template = LoginTemplate { userid, name }; + HtmlTemplate(template) +} + +// Valid user session required. If there is none, redirect to the auth page +async fn dashboard(user: User) -> impl IntoResponse { + let name = user.username; + let userid = true; + let template = DashboardTemplate { userid, name }; + HtmlTemplate(template) +} + +#[derive(Template)] +#[template(path = "login.html")] +struct LoginTemplate { + userid: bool, + name: String, +} + +#[derive(Template)] +#[template(path = "dashboard.html")] +struct DashboardTemplate { + userid: bool, + name: String, +} + +struct FreshUserId { + pub user_id: UserId, + pub cookie: HeaderValue, +} + +enum UserIdFromSession { + FoundUserId(UserId), + CreatedFreshUserId(FreshUserId), +} + +#[async_trait] +impl FromRequest for UserIdFromSession +where + B: Send, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: &mut RequestParts) -> Result { + let Extension(store) = Extension::::from_request(req) + .await + .expect("`MemoryStore` extension missing"); + + let cookie = Option::>::from_request(req) + .await + .unwrap(); + + let session_cookie = cookie.as_ref().and_then(|cookie| cookie.get(COOKIE_NAME)); + + // return the new created session cookie for client + if session_cookie.is_none() { + let user_id = UserId::new(); + let mut session = Session::new(); + session.insert("user_id", user_id).unwrap(); + let cookie = store.store_session(session).await.unwrap().unwrap(); + return Ok(Self::CreatedFreshUserId(FreshUserId { + user_id, + cookie: HeaderValue::from_str(format!("{}={}", COOKIE_NAME, cookie).as_str()) + .unwrap(), + })); + } + + tracing::debug!( + "UserIdFromSession: got session cookie from user agent, {}={}", + COOKIE_NAME, + session_cookie.unwrap() + ); + // continue to decode the session cookie + let user_id = if let Some(session) = store + .load_session(session_cookie.unwrap().to_owned()) + .await + .unwrap() + { + if let Some(user_id) = session.get::("user_id") { + tracing::debug!( + "UserIdFromSession: session decoded success, user_id={:?}", + user_id + ); + user_id + } else { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "No `user_id` found in session", + )); + } + } else { + tracing::debug!( + "UserIdFromSession: err session not exists in store, {}={}", + COOKIE_NAME, + session_cookie.unwrap() + ); + return Err((StatusCode::BAD_REQUEST, "No session found for cookie")); + }; + + Ok(Self::FoundUserId(user_id)) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +struct UserId(Uuid); + +impl UserId { + fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate { + userid: bool, + name: String, +} + +struct HtmlTemplate(T); + +impl IntoResponse for HtmlTemplate +where + T: Template, +{ + fn into_response(self) -> Response { + match self.0.render() { + Ok(html) => Html(html).into_response(), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to render template. Error: {}", err), + ) + .into_response(), + } + } +} + +async fn logout( + Extension(store): Extension, + TypedHeader(cookies): TypedHeader, +) -> impl IntoResponse { + let cookie = cookies.get(COOKIE_NAME).unwrap(); + let session = match store.load_session(cookie.to_string()).await.unwrap() { + Some(s) => s, + // No session active, just redirect + None => return Redirect::to(&"/"), + }; + + store.destroy_session(session).await.unwrap(); + + Redirect::to(&"/") +} + +struct AuthRedirect; + +impl IntoResponse for AuthRedirect { + fn into_response(self) -> Response { + Redirect::temporary(&"/login").into_response() + } +} + +#[async_trait] +impl FromRequest for User +where + B: Send, +{ + // If anything goes wrong or no session is found, redirect to the auth page + type Rejection = AuthRedirect; + + async fn from_request(req: &mut RequestParts) -> Result { + let Extension(store) = Extension::::from_request(req) + .await + .expect("`MemoryStore` extension is missing"); + + let cookies = TypedHeader::::from_request(req) + .await + .map_err(|e| match *e.name() { + header::COOKIE => match e.reason() { + TypedHeaderRejectionReason::Missing => AuthRedirect, + _ => panic!("unexpected error getting Cookie header(s): {}", e), + }, + _ => panic!("unexpected error getting cookies: {}", e), + })?; + let session_cookie = cookies.get(COOKIE_NAME).ok_or(AuthRedirect)?; + + let session = store + .load_session(session_cookie.to_string()) + .await + .unwrap() + .ok_or(AuthRedirect)?; + + let user = session.get::("user").ok_or(AuthRedirect)?; + + Ok(user) + } +} diff --git a/templates/base.html b/templates/base.html index f96f7ba..0fe8bad 100644 --- a/templates/base.html +++ b/templates/base.html @@ -29,7 +29,7 @@ - {% if userid %} + {% if logged_in %} {% else %}