From bc815a5cf16f8cef84041078cf5ef85514e7942f Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Nov 2025 22:17:29 +0100 Subject: [PATCH] Add users authentication routes --- README.md | 4 +- matrixgw_backend/.gitignore | 1 + matrixgw_backend/Cargo.lock | 948 +++++++++++------- matrixgw_backend/Cargo.toml | 16 +- matrixgw_backend/docker-compose.yml | 13 - matrixgw_backend/docker/dex/dex.config.yaml | 2 +- matrixgw_backend/examples/api_curl.rs | 9 +- matrixgw_backend/src/app_config.rs | 78 +- matrixgw_backend/src/constants.rs | 15 + .../src/controllers/auth_controller.rs | 131 +++ matrixgw_backend/src/controllers/mod.rs | 33 + .../src/extractors/auth_extractor.rs | 305 ++++++ matrixgw_backend/src/extractors/mod.rs | 2 + .../src/extractors/session_extractor.rs | 91 ++ matrixgw_backend/src/lib.rs | 4 + matrixgw_backend/src/main.rs | 24 +- matrixgw_backend/src/users.rs | 168 ++++ matrixgw_backend/src/utils/crypt_utils.rs | 6 + matrixgw_backend/src/utils/mod.rs | 3 + matrixgw_backend/src/utils/rand_utils.rs | 6 + matrixgw_backend/src/utils/time_utils.rs | 9 + 21 files changed, 1417 insertions(+), 451 deletions(-) create mode 100644 matrixgw_backend/src/constants.rs create mode 100644 matrixgw_backend/src/controllers/auth_controller.rs create mode 100644 matrixgw_backend/src/extractors/auth_extractor.rs create mode 100644 matrixgw_backend/src/extractors/mod.rs create mode 100644 matrixgw_backend/src/extractors/session_extractor.rs create mode 100644 matrixgw_backend/src/users.rs create mode 100644 matrixgw_backend/src/utils/crypt_utils.rs create mode 100644 matrixgw_backend/src/utils/mod.rs create mode 100644 matrixgw_backend/src/utils/rand_utils.rs create mode 100644 matrixgw_backend/src/utils/time_utils.rs diff --git a/README.md b/README.md index 8cc8aa6..0c7620e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ docker run --rm -it docker.io/pierre42100/matrix_gateway --help ### Dependencies ``` cd matrixgw_backend -mkdir -p storage/maspostgres storage/synapse storage/minio +mkdir -p storage/maspostgres storage/synapse docker compose up ``` @@ -35,14 +35,12 @@ URLs: * Synapse: http://localhost:8448/ * Matrix Authentication Service: http://localhost:8778/ * OpenID configuration: http://127.0.0.1:9001/dex/.well-known/openid-configuration -* Minio console: http://localhost:9002/ Auto-created Matrix accounts: * `admin1` : `admin1` * `user1` : `user1` -Minio administration credentials: `minioadmin` : `minioadmin` ### Backend ```bash diff --git a/matrixgw_backend/.gitignore b/matrixgw_backend/.gitignore index 79e4ba8..f3a3b05 100644 --- a/matrixgw_backend/.gitignore +++ b/matrixgw_backend/.gitignore @@ -1,3 +1,4 @@ storage +app_storage .idea target diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index a6569f5..ad5c8da 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ "flate2", "foldhash", "futures-core", - "h2", + "h2 0.3.27", "http 0.2.12", "httparse", "httpdate", @@ -342,15 +342,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "async-trait" -version = "0.1.89" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii_utils" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "atomic-waker" @@ -358,53 +365,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64 0.22.1", - "http 1.3.1", - "log", - "native-tls", - "serde", - "serde_json", - "url", -] - [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "aws-creds" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13804829a843b3f26e151c97acbb315ee1177a2724690edfcd28f1894146200" -dependencies = [ - "attohttpc", - "home", - "log", - "quick-xml", - "rust-ini", - "serde", - "thiserror", - "time", - "url", -] - -[[package]] -name = "aws-region" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5532f65342f789f9c1b7078ea9c9cd9293cd62dcc284fa99adc4a1c9ba43469c" -dependencies = [ - "thiserror", -] - [[package]] name = "backon" version = "1.6.0" @@ -414,6 +380,18 @@ dependencies = [ "fastrand", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base16ct" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" + [[package]] name = "base64" version = "0.20.0" @@ -426,12 +404,35 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "binstring" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef" + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -483,15 +484,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" version = "1.2.44" @@ -560,6 +552,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "coarsetime" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -581,37 +584,16 @@ dependencies = [ ] [[package]] -name = "compact_str" -version = "0.7.1" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const-random" -version = "0.1.18" +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "tiny-keccak", -] +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cookie" @@ -666,10 +648,16 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.4" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] [[package]] name = "crypto-common" @@ -682,6 +670,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" + [[package]] name = "ctr" version = "0.9.2" @@ -691,6 +685,17 @@ dependencies = [ "cipher", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -698,7 +703,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -729,6 +733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -745,12 +750,48 @@ dependencies = [ ] [[package]] -name = "dlv-list" -version = "0.5.2" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "const-random", + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom 0.2.16", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -801,12 +842,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fast_chemail" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" +dependencies = [ + "ascii_utils", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -874,12 +934,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-macro" version = "0.3.31" @@ -910,11 +964,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -928,6 +980,7 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -937,8 +990,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -963,6 +1018,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.27" @@ -983,10 +1049,23 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "h2" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] name = "hashbrown" @@ -1025,12 +1104,27 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.12" +name = "hmac-sha1-compact" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" + +[[package]] +name = "hmac-sha256" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" dependencies = [ - "windows-sys 0.61.2", + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280" +dependencies = [ + "digest", ] [[package]] @@ -1100,6 +1194,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body", "httparse", @@ -1111,6 +1206,22 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1146,9 +1257,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1266,7 +1379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown", ] [[package]] @@ -1283,6 +1396,9 @@ name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "serde", +] [[package]] name = "iri-string" @@ -1350,6 +1466,46 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad8761f175784dfbb83709f322fc4daf6b27afd5bf375492f2876f9e925ef5a" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand 0.8.5", + "serde", + "serde_json", + "superboring", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1361,6 +1517,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1368,6 +1527,26 @@ version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "light-openid" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a15777d080e807d5b6b3c0b5a293f7d4680d74a4c66b0cdf9db0441ea9f548" +dependencies = [ + "base64 0.22.1", + "log", + "reqwest", + "serde", + "serde_json", + "urlencoding", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1412,6 +1591,16 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "mailchecker" +version = "6.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abad4bc63045f04cfc55aa4c55d4ec0a890c377ce56463bfc2adc2bc059c4b84" +dependencies = [ + "fast_chemail", + "once_cell", +] + [[package]] name = "matrixgw_backend" version = "0.1.0" @@ -1420,32 +1609,27 @@ dependencies = [ "actix-session", "actix-web", "anyhow", + "base16ct 0.3.0", + "bytes", "clap", "env_logger", + "futures-util", + "hex", + "ipnet", + "jwt-simple", "lazy_static", + "light-openid", "log", - "rust-s3", + "mailchecker", + "rand 0.9.2", "serde", + "sha2", + "thiserror", "tokio", + "urlencoding", + "uuid", ] -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "md5" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" - [[package]] name = "memchr" version = "2.7.6" @@ -1458,15 +1642,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minidom" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d" -dependencies = [ - "rxml", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1506,15 +1681,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -1525,6 +1691,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c79c15c05d4bf82b6f5ef163104cc81a760d8e874d38ac50ab67c8877b647b" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1540,6 +1722,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1547,25 +1740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" -dependencies = [ - "libc", - "objc2-core-foundation", + "libm", ] [[package]] @@ -1631,13 +1806,27 @@ dependencies = [ ] [[package]] -name = "ordered-multimap" -version = "0.7.3" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "dlv-list", - "hashbrown 0.14.5", + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", ] [[package]] @@ -1663,6 +1852,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1681,6 +1879,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1738,6 +1957,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -1747,16 +1975,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-xml" -version = "0.38.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quote" version = "1.0.41" @@ -1907,16 +2125,19 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", - "futures-util", + "h2 0.4.12", "http 1.3.1", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -1927,60 +2148,58 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", ] [[package]] -name = "rust-ini" -version = "0.21.3" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "cfg-if", - "ordered-multimap", + "hmac", + "subtle", ] [[package]] -name = "rust-s3" -version = "0.37.0" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f9b973bd4097f5bb47e5827dcb9fb5dc17e93879e46badc27d2a4e9a4e5588" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "async-trait", - "aws-creds", - "aws-region", - "base64 0.22.1", - "bytes", + "cc", "cfg-if", - "futures-util", - "hex", - "hmac", - "http 1.3.1", - "log", - "maybe-async", - "md5", - "minidom", - "percent-encoding", - "quick-xml", - "reqwest", - "serde", - "serde_derive", - "serde_json", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", "sha2", - "sysinfo", - "thiserror", - "time", - "tokio", - "tokio-stream", - "url", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] @@ -1996,6 +2215,19 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -2005,31 +2237,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rxml" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" -dependencies = [ - "bytes", - "rxml_validation", -] - -[[package]] -name = "rxml_validation" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" -dependencies = [ - "compact_str", -] - [[package]] name = "ryu" version = "1.0.20" @@ -2051,6 +2275,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2166,6 +2404,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2204,18 +2452,28 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" @@ -2228,6 +2486,19 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "superboring" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f" +dependencies = [ + "getrandom 0.2.16", + "hmac-sha256", + "hmac-sha512", + "rand 0.8.5", + "rsa", +] + [[package]] name = "syn" version = "2.0.108" @@ -2260,17 +2531,24 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.37.2" +name = "system-configuration" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows", ] [[package]] @@ -2337,15 +2615,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -2395,13 +2664,12 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.17" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls", "tokio", ] @@ -2529,6 +2797,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -2541,6 +2815,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2553,6 +2833,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2589,6 +2881,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" version = "0.2.105" @@ -2647,19 +2948,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.82" @@ -2670,96 +2958,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-link" version = "0.1.3" @@ -2773,13 +2971,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-numerics" -version = "0.2.0" +name = "windows-registry" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-core", "windows-link 0.1.3", + "windows-result", + "windows-strings", ] [[package]] @@ -2860,15 +3059,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index 9ae5242..dfb0d9e 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -10,8 +10,20 @@ clap = { version = "4.5.51", features = ["derive", "env"] } lazy_static = "1.5.0" anyhow = "1.0.100" serde = { version = "1.0.228", features = ["derive"] } -rust-s3 = { version = "0.37.0", features = ["tokio"] } tokio = { version = "1.48.0", features = ["full"] } actix-web = "4.11.0" actix-session = { version = "0.11.0", features = ["redis-session"] } -actix-remote-ip = "0.1.0" \ No newline at end of file +actix-remote-ip = "0.1.0" +light-openid = "1.0.4" +bytes = "1.10.1" +sha2 = "0.10.9" +urlencoding = "2.1.3" +base16ct = { version = "0.3.0", features = ["alloc"] } +futures-util = "0.3.31" +jwt-simple = { version = "0.12.13", default-features = false, features = ["pure-rust"] } +thiserror = "2.0.17" +uuid = { version = "1.18.1", features = ["v4", "serde"] } +ipnet = { version = "2.11.0", features = ["serde"] } +rand = "0.9.2" +hex = "0.4.3" +mailchecker = "6.0.19" \ No newline at end of file diff --git a/matrixgw_backend/docker-compose.yml b/matrixgw_backend/docker-compose.yml index fec7c62..be4a7bb 100644 --- a/matrixgw_backend/docker-compose.yml +++ b/matrixgw_backend/docker-compose.yml @@ -92,19 +92,6 @@ services: - ./docker/dex:/conf:ro command: [ "dex", "serve", "/conf/dex.config.yaml" ] - minio: - image: quay.io/minio/minio - command: minio server --console-address ":9002" /data - ports: - - 9000:9000/tcp - - 9002:9002/tcp - environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin - volumes: - # You may store the database tables in a local folder.. - - ./storage/minio:/data - redis: image: redis:alpine command: redis-server --requirepass ${REDIS_PASS:-secretredis} diff --git a/matrixgw_backend/docker/dex/dex.config.yaml b/matrixgw_backend/docker/dex/dex.config.yaml index 7704800..0b9d381 100644 --- a/matrixgw_backend/docker/dex/dex.config.yaml +++ b/matrixgw_backend/docker/dex/dex.config.yaml @@ -22,5 +22,5 @@ staticClients: - id: foo secret: bar redirectURIs: - - http://localhost:8000/oidc_cb + - http://localhost:5173/oidc_cb name: Project diff --git a/matrixgw_backend/examples/api_curl.rs b/matrixgw_backend/examples/api_curl.rs index a2669fd..eb57533 100644 --- a/matrixgw_backend/examples/api_curl.rs +++ b/matrixgw_backend/examples/api_curl.rs @@ -1,11 +1,12 @@ use clap::Parser; use jwt_simple::algorithms::HS256Key; use jwt_simple::prelude::{Clock, Duration, JWTClaims, MACLike}; -use matrix_gateway::extractors::client_auth::TokenClaims; -use matrix_gateway::utils::base_utils::rand_str; +use matrixgw_backend::constants; +use matrixgw_backend::extractors::auth_extractor::TokenClaims; use std::ops::Add; use std::os::unix::prelude::CommandExt; use std::process::Command; +use matrixgw_backend::utils::rand_utils::rand_string; /// cURL wrapper to query MatrixGW #[derive(Parser, Debug)] @@ -59,7 +60,7 @@ fn main() { subject: None, audiences: None, jwt_id: None, - nonce: Some(rand_str(10)), + nonce: Some(rand_string(10)), custom: TokenClaims { method: args.method.to_string(), uri: args.uri, @@ -78,7 +79,7 @@ fn main() { let _ = Command::new("curl") .args(["-X", &args.method]) - .args(["-H", &format!("x-client-auth: {jwt}")]) + .args(["-H", &format!("{}: {jwt}", constants::API_AUTH_HEADER)]) .args(args.run) .arg(full_url) .exec(); diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index 7552544..ed122d8 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -1,6 +1,7 @@ +use crate::users::{APITokenID, UserEmail}; +use crate::utils::crypt_utils::sha256str; use clap::Parser; -use s3::creds::Credentials; -use s3::{Bucket, Region}; +use std::path::{Path, PathBuf}; /// Matrix gateway backend API #[derive(Parser, Debug, Clone)] @@ -11,7 +12,7 @@ pub struct AppConfig { pub listen_address: String, /// Website origin - #[clap(short, long, env, default_value = "http://localhost:8000")] + #[clap(short, long, env, default_value = "http://localhost:5173")] pub website_origin: String, /// Proxy IP, might end with a star "*" @@ -75,29 +76,9 @@ pub struct AppConfig { #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] oidc_redirect_url: String, - /// S3 Bucket name - #[arg(long, env, default_value = "matrix-gw")] - s3_bucket_name: String, - - /// S3 region (if not using Minio) - #[arg(long, env, default_value = "eu-central-1")] - s3_region: String, - - /// S3 API endpoint - #[arg(long, env, default_value = "http://localhost:9000")] - s3_endpoint: String, - - /// S3 access key - #[arg(long, env, default_value = "minioadmin")] - s3_access_key: String, - - /// S3 secret key - #[arg(long, env, default_value = "minioadmin")] - s3_secret_key: String, - - /// S3 skip auto create bucket if not existing - #[arg(long, env)] - pub s3_skip_auto_create_bucket: bool, + /// Application storage path + #[arg(long, env, default_value = "app_storage")] + storage_path: String, } lazy_static::lazy_static! { @@ -113,10 +94,10 @@ impl AppConfig { } /// Get auto login email (if not empty) - pub fn unsecure_auto_login_email(&self) -> Option<&str> { + pub fn unsecure_auto_login_email(&self) -> Option { match self.unsecure_auto_login_email.as_deref() { None | Some("") => None, - s => s, + Some(s) => Some(UserEmail(s.to_owned())), } } @@ -165,28 +146,29 @@ impl AppConfig { } } - /// Get s3 bucket credentials - pub fn s3_credentials(&self) -> anyhow::Result { - Ok(Credentials::new( - Some(&self.s3_access_key), - Some(&self.s3_secret_key), - None, - None, - None, - )?) + /// Get storage path + pub fn storage_path(&self) -> &Path { + Path::new(self.storage_path.as_str()) } - /// Get S3 bucket - pub fn s3_bucket(&self) -> anyhow::Result> { - Ok(Bucket::new( - &self.s3_bucket_name, - Region::Custom { - region: self.s3_region.to_string(), - endpoint: self.s3_endpoint.to_string(), - }, - self.s3_credentials()?, - )? - .with_path_style()) + /// User storage directory + pub fn user_directory(&self, mail: &UserEmail) -> PathBuf { + self.storage_path().join("users").join(sha256str(&mail.0)) + } + + /// User metadata file + pub fn user_metadata_file_path(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("metadata.json") + } + + /// User API tokens directory + pub fn user_api_token_directory(&self, mail: &UserEmail) -> PathBuf { + self.user_directory(mail).join("api-tokens") + } + + /// User API token metadata file + pub fn user_api_token_metadata_file(&self, mail: &UserEmail, id: &APITokenID) -> PathBuf { + self.user_api_token_directory(mail).join(id.0.to_string()) } } diff --git a/matrixgw_backend/src/constants.rs b/matrixgw_backend/src/constants.rs new file mode 100644 index 0000000..d46042c --- /dev/null +++ b/matrixgw_backend/src/constants.rs @@ -0,0 +1,15 @@ +/// Auth header +pub const API_AUTH_HEADER: &str = "x-client-auth"; + +/// Max token validity, in seconds +pub const API_TOKEN_JWT_MAX_DURATION: u64 = 15 * 60; + +/// Session-specific constants +pub mod sessions { + /// OpenID auth session state key + pub const OIDC_STATE_KEY: &str = "oidc-state"; + /// OpenID auth remote IP address + pub const OIDC_REMOTE_IP: &str = "oidc-remote-ip"; + /// Authenticated ID + pub const USER_ID: &str = "uid"; +} diff --git a/matrixgw_backend/src/controllers/auth_controller.rs b/matrixgw_backend/src/controllers/auth_controller.rs new file mode 100644 index 0000000..ab14ace --- /dev/null +++ b/matrixgw_backend/src/controllers/auth_controller.rs @@ -0,0 +1,131 @@ +use crate::app_config::AppConfig; +use crate::controllers::{HttpFailure, HttpResult}; +use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; +use crate::extractors::session_extractor::MatrixGWSession; +use crate::users::{User, UserEmail}; +use actix_remote_ip::RemoteIP; +use actix_web::{HttpResponse, web}; +use light_openid::primitives::OpenIDConfig; + +#[derive(serde::Serialize)] +struct StartOIDCResponse { + url: String, +} + +/// Start OIDC authentication +pub async fn start_oidc(session: MatrixGWSession, remote_ip: RemoteIP) -> HttpResult { + let prov = AppConfig::get().openid_provider(); + + let conf = match OpenIDConfig::load_from_url(prov.configuration_url).await { + Ok(c) => c, + Err(e) => { + log::error!("Failed to fetch OpenID provider configuration! {e}"); + return Ok(HttpResponse::InternalServerError() + .json("Failed to fetch OpenID provider configuration!")); + } + }; + + let state = match session.gen_oidc_state(remote_ip.0) { + Ok(s) => s, + Err(e) => { + log::error!("Failed to generate auth state! {e}"); + return Ok(HttpResponse::InternalServerError().json("Failed to generate auth state!")); + } + }; + + Ok(HttpResponse::Ok().json(StartOIDCResponse { + url: conf.gen_authorization_url( + prov.client_id, + &state, + &AppConfig::get().openid_provider().redirect_url, + ), + })) +} + +#[derive(serde::Deserialize)] +pub struct FinishOpenIDLoginQuery { + code: String, + state: String, +} + +/// Finish OIDC authentication +pub async fn finish_oidc( + session: MatrixGWSession, + remote_ip: RemoteIP, + req: web::Json, +) -> HttpResult { + if let Err(e) = session.validate_state(&req.state, remote_ip.0) { + log::error!("Failed to validate OIDC CB state! {e}"); + return Ok(HttpResponse::BadRequest().json("Invalid state!")); + } + + let prov = AppConfig::get().openid_provider(); + + let conf = OpenIDConfig::load_from_url(prov.configuration_url) + .await + .map_err(HttpFailure::OpenID)?; + + let (token, _) = conf + .request_token( + prov.client_id, + prov.client_secret, + &req.code, + &AppConfig::get().openid_provider().redirect_url, + ) + .await + .map_err(HttpFailure::OpenID)?; + let (user_info, _) = conf + .request_user_info(&token) + .await + .map_err(HttpFailure::OpenID)?; + + if user_info.email_verified != Some(true) { + log::error!("Email is not verified!"); + return Ok(HttpResponse::Unauthorized().json("Email unverified by IDP!")); + } + + let mail = match user_info.email { + Some(m) => m, + None => { + return Ok(HttpResponse::Unauthorized().json("Email not provided by the IDP!")); + } + }; + + let user_name = user_info.name.unwrap_or_else(|| { + format!( + "{} {}", + user_info.given_name.as_deref().unwrap_or(""), + user_info.family_name.as_deref().unwrap_or("") + ) + }); + + let user = User::create_or_update_user(&UserEmail(mail), &user_name).await?; + + session.set_user(&user)?; + + Ok(HttpResponse::Ok().finish()) +} + +/// Get current user information +pub async fn auth_info(auth: AuthExtractor) -> HttpResult { + Ok(HttpResponse::Ok().json(auth.user)) +} + +/// Sign out user +pub async fn sign_out(auth: AuthExtractor, session: MatrixGWSession) -> HttpResult { + match auth.method { + AuthenticatedMethod::Cookie => { + session.unset_current_user()?; + } + + AuthenticatedMethod::Token(token) => { + token.delete(&auth.user.email).await?; + } + + AuthenticatedMethod::Dev => { + // Nothing to be done, user is always authenticated + } + } + + Ok(HttpResponse::NoContent().finish()) +} diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs index 98a0949..25104bf 100644 --- a/matrixgw_backend/src/controllers/mod.rs +++ b/matrixgw_backend/src/controllers/mod.rs @@ -1 +1,34 @@ +use actix_web::http::StatusCode; +use actix_web::{HttpResponse, ResponseError}; +use std::error::Error; + +pub mod auth_controller; pub mod server_controller; + +#[derive(thiserror::Error, Debug)] +pub enum HttpFailure { + #[error("this resource requires higher privileges")] + Forbidden, + #[error("this resource was not found")] + NotFound, + #[error("an unspecified open id error occurred: {0}")] + OpenID(Box), + #[error("an unspecified internal error occurred: {0}")] + InternalError(#[from] anyhow::Error), +} + +impl ResponseError for HttpFailure { + fn status_code(&self) -> StatusCode { + match &self { + Self::Forbidden => StatusCode::FORBIDDEN, + Self::NotFound => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + HttpResponse::build(self.status_code()).body(self.to_string()) + } +} + +pub type HttpResult = Result; diff --git a/matrixgw_backend/src/extractors/auth_extractor.rs b/matrixgw_backend/src/extractors/auth_extractor.rs new file mode 100644 index 0000000..8c75a29 --- /dev/null +++ b/matrixgw_backend/src/extractors/auth_extractor.rs @@ -0,0 +1,305 @@ +use crate::app_config::AppConfig; +use crate::constants; +use crate::extractors::session_extractor::MatrixGWSession; +use crate::users::{APIToken, APITokenID, User, UserEmail}; +use crate::utils::time_utils::time_secs; +use actix_remote_ip::RemoteIP; +use actix_web::dev::Payload; +use actix_web::error::ErrorPreconditionFailed; +use actix_web::{FromRequest, HttpRequest}; +use anyhow::Context; +use bytes::Bytes; +use jwt_simple::common::VerificationOptions; +use jwt_simple::prelude::{Duration, HS256Key, MACLike}; +use sha2::{Digest, Sha256}; +use std::fmt::Display; +use std::net::IpAddr; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub enum AuthenticatedMethod { + /// User is authenticated using a cookie + Cookie, + /// User is authenticated through command line, for debugging purposes only + Dev, + /// User is authenticated using an API token + Token(APIToken), +} + +pub struct AuthExtractor { + pub user: User, + pub method: AuthenticatedMethod, + pub payload: Option>, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct MatrixJWTKID { + pub user_email: UserEmail, + pub id: APITokenID, +} + +impl Display for MatrixJWTKID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}#{}", self.user_email.0, self.id.0) + } +} + +impl FromStr for MatrixJWTKID { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let (mail, token_id) = s + .split_once("#") + .context("Failed to decode KID in two parts!")?; + + let mail = UserEmail(mail.to_string()); + + if !mail.is_valid() { + anyhow::bail!("Given email is invalid!") + } + + Ok(Self { + user_email: mail, + id: token_id.parse().context("Failed to parse API token ID")?, + }) + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenClaims { + #[serde(rename = "met")] + pub method: String, + pub uri: String, + #[serde(rename = "pay", skip_serializing_if = "Option::is_none")] + pub payload_sha256: Option, +} + +impl AuthExtractor { + async fn extract_auth( + req: &HttpRequest, + remote_ip: IpAddr, + payload_bytes: Option, + ) -> Result { + // Check for authentication using API token + if let Some(token) = req.headers().get(constants::API_AUTH_HEADER) { + let Ok(jwt_token) = token.to_str() else { + return Err(actix_web::error::ErrorBadRequest( + "Failed to decode token as string!", + )); + }; + + let metadata = match jwt_simple::token::Token::decode_metadata(jwt_token) { + Ok(m) => m, + Err(e) => { + log::error!("Failed to decode JWT header metadata! {e}"); + return Err(actix_web::error::ErrorBadRequest( + "Failed to decode JWT header metadata!", + )); + } + }; + + // Extract token ID + let Some(kid) = metadata.key_id() else { + return Err(actix_web::error::ErrorBadRequest( + "Missing key id in request!", + )); + }; + + let jwt_kid = match MatrixJWTKID::from_str(kid) { + Ok(i) => i, + Err(e) => { + log::error!("Failed to parse token id! {e}"); + return Err(actix_web::error::ErrorBadRequest( + "Failed to parse token id!", + )); + } + }; + + // Get token information + let Ok(mut token) = APIToken::load(&jwt_kid.user_email, &jwt_kid.id).await else { + log::error!("Token not found!"); + return Err(actix_web::error::ErrorForbidden("Token not found!")); + }; + + // Decode JWT + let key = HS256Key::from_bytes(token.secret.as_ref()); + let verif = VerificationOptions { + max_validity: Some(Duration::from_secs(constants::API_TOKEN_JWT_MAX_DURATION)), + ..Default::default() + }; + + let claims = match key.verify_token::(jwt_token, Some(verif)) { + Ok(t) => t, + Err(e) => { + log::error!("JWT validation failed! {e}"); + return Err(actix_web::error::ErrorForbidden("JWT validation failed!")); + } + }; + + // Check for nonce + if claims.nonce.is_none() { + return Err(actix_web::error::ErrorBadRequest( + "A nonce is required in auth JWT!", + )); + } + + // Check IP restriction + if let Some(net) = token.network + && !net.contains(&remote_ip) + { + log::error!( + "Trying to use token {:?} from unauthorized IP address: {remote_ip:?}", + token.id + ); + return Err(actix_web::error::ErrorForbidden( + "This token cannot be used from this IP address!", + )); + } + + // Check for write access + if token.read_only && !req.method().is_safe() { + return Err(actix_web::error::ErrorBadRequest( + "Read only token cannot perform write operations!", + )); + } + + // Get user information + let Ok(user) = User::get_by_mail(&jwt_kid.user_email).await else { + return Err(actix_web::error::ErrorBadRequest( + "Failed to get user information from token!", + )); + }; + + // Update last use (if needed) + if token.shall_update_time_used() { + token.last_used = time_secs(); + if let Err(e) = token.write(&jwt_kid.user_email).await { + log::error!("Failed to refresh last usage of token! {e}"); + } + } + + // Handle tokens expiration + if token.is_expired() { + log::error!("Attempted to use expired token! {token:?}"); + return Err(actix_web::error::ErrorBadRequest("Token has expired!")); + } + + // Check payload + let payload = match (payload_bytes, claims.custom.payload_sha256) { + (None, _) => None, + (Some(_), None) => { + return Err(actix_web::error::ErrorBadRequest( + "A payload digest must be included in the JWT when the request has a payload!", + )); + } + (Some(payload), Some(provided_digest)) => { + let computed_digest = base16ct::lower::encode_string(&Sha256::digest(&payload)); + if computed_digest != provided_digest { + log::error!( + "Expected digest {provided_digest} for payload but computed {computed_digest}!" + ); + return Err(actix_web::error::ErrorBadRequest( + "Computed digest is different from the one provided in the JWT!", + )); + } + + Some(payload.to_vec()) + } + }; + + return Ok(Self { + method: AuthenticatedMethod::Token(token), + user, + payload, + }); + } + + // Check if login is hard-coded as program argument + if let Some(email) = &AppConfig::get().unsecure_auto_login_email() { + let user = User::get_by_mail(email).await.map_err(|e| { + log::error!("Failed to retrieve dev user: {e}"); + ErrorPreconditionFailed("Unable to retrieve dev user!") + })?; + return Ok(Self { + method: AuthenticatedMethod::Dev, + user, + payload: payload_bytes.map(|bytes| bytes.to_vec()), + }); + } + + // Check for cookie authentication + let session = MatrixGWSession::extract(req).await?; + if let Some(mail) = session.current_user().map_err(|e| { + log::error!("Failed to retrieve user id: {e}"); + ErrorPreconditionFailed("Failed to read session information!") + })? { + let user = User::get_by_mail(&mail).await.map_err(|e| { + log::error!("Failed to retrieve user from cookie session: {e}"); + ErrorPreconditionFailed("Failed to retrieve user information!") + })?; + return Ok(Self { + method: AuthenticatedMethod::Cookie, + user, + payload: payload_bytes.map(|bytes| bytes.to_vec()), + }); + }; + + Err(ErrorPreconditionFailed("Authentication required!")) + } +} + +impl FromRequest for AuthExtractor { + type Error = actix_web::Error; + type Future = futures_util::future::LocalBoxFuture<'static, Result>; + + fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { + let req = req.clone(); + + let remote_ip = match RemoteIP::from_request(&req, &mut Payload::None).into_inner() { + Ok(ip) => ip, + Err(e) => return Box::pin(async { Err(e) }), + }; + + let mut payload = payload.take(); + + Box::pin(async move { + let payload_bytes = match Bytes::from_request(&req, &mut payload).await { + Ok(b) => { + if b.is_empty() { + None + } else { + Some(b) + } + } + Err(e) => { + log::error!("Failed to extract request payload! {e}"); + None + } + }; + + Self::extract_auth(&req, remote_ip.0, payload_bytes).await + }) + } +} + +#[cfg(test)] +mod tests { + use crate::extractors::auth_extractor::MatrixJWTKID; + use crate::users::{APITokenID, UserEmail}; + use std::str::FromStr; + + #[test] + fn encode_decode_jwt_kid() { + let src = MatrixJWTKID { + user_email: UserEmail("test@mail.com".to_string()), + id: APITokenID::default(), + }; + let encoded = src.to_string(); + let decoded = encoded.parse::().unwrap(); + assert_eq!(src, decoded); + + MatrixJWTKID::from_str("bad").unwrap_err(); + MatrixJWTKID::from_str("ba#d").unwrap_err(); + MatrixJWTKID::from_str("test@valid.com#d").unwrap_err(); + } +} diff --git a/matrixgw_backend/src/extractors/mod.rs b/matrixgw_backend/src/extractors/mod.rs new file mode 100644 index 0000000..4e4f77f --- /dev/null +++ b/matrixgw_backend/src/extractors/mod.rs @@ -0,0 +1,2 @@ +pub mod auth_extractor; +pub mod session_extractor; diff --git a/matrixgw_backend/src/extractors/session_extractor.rs b/matrixgw_backend/src/extractors/session_extractor.rs new file mode 100644 index 0000000..b33dc59 --- /dev/null +++ b/matrixgw_backend/src/extractors/session_extractor.rs @@ -0,0 +1,91 @@ +use crate::constants; +use crate::users::{User, UserEmail}; +use crate::utils::rand_utils::rand_string; +use actix_session::Session; +use actix_web::dev::Payload; +use actix_web::{Error, FromRequest, HttpRequest}; +use futures_util::future::{Ready, ready}; +use std::net::IpAddr; + +/// Matrix Gateway session errors +#[derive(thiserror::Error, Debug)] +enum MatrixGWSessionError { + #[error("Missing state!")] + OIDCMissingState, + #[error("Missing IP address!")] + OIDCMissingIP, + #[error("Invalid state!")] + OIDCInvalidState, + #[error("Invalid IP address!")] + OIDCInvalidIP, +} + +/// Matrix Gateway session +/// +/// Basic wrapper around actix-session extractor +pub struct MatrixGWSession(Session); + +impl MatrixGWSession { + /// Generate OpenID state for this session + pub fn gen_oidc_state(&self, ip: IpAddr) -> anyhow::Result { + let random_string = rand_string(50); + self.0 + .insert(constants::sessions::OIDC_STATE_KEY, random_string.clone())?; + self.0.insert(constants::sessions::OIDC_REMOTE_IP, ip)?; + Ok(random_string) + } + + /// Validate OpenID state + pub fn validate_state(&self, state: &str, ip: IpAddr) -> anyhow::Result<()> { + let session_state: String = self + .0 + .get(constants::sessions::OIDC_STATE_KEY)? + .ok_or(MatrixGWSessionError::OIDCMissingState)?; + + let session_ip: IpAddr = self + .0 + .get(constants::sessions::OIDC_REMOTE_IP)? + .ok_or(MatrixGWSessionError::OIDCMissingIP)?; + + if session_state != state { + return Err(anyhow::anyhow!(MatrixGWSessionError::OIDCInvalidState)); + } + + if session_ip != ip { + return Err(anyhow::anyhow!(MatrixGWSessionError::OIDCInvalidIP)); + } + + Ok(()) + } + + /// Set current user + pub fn set_user(&self, user: &User) -> anyhow::Result<()> { + self.0.insert(constants::sessions::USER_ID, &user.email)?; + Ok(()) + } + + /// Get current user + pub fn current_user(&self) -> anyhow::Result> { + Ok(self.0.get(constants::sessions::USER_ID)?) + } + + /// Remove defined user + pub fn unset_current_user(&self) -> anyhow::Result<()> { + self.0.remove(constants::sessions::USER_ID); + Ok(()) + } +} + +impl FromRequest for MatrixGWSession { + type Error = Error; + type Future = Ready>; + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + ready( + Session::from_request(req, &mut Payload::None) + .into_inner() + .map(MatrixGWSession), + ) + } +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 2483671..82c2228 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -1,2 +1,6 @@ pub mod app_config; +pub mod constants; pub mod controllers; +pub mod extractors; +pub mod users; +pub mod utils; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 3d6132f..3100299 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -5,7 +5,8 @@ use actix_session::storage::RedisSessionStore; use actix_web::cookie::Key; use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; -use matrixgw_backend::controllers::server_controller; +use matrixgw_backend::controllers::{auth_controller, server_controller}; +use matrixgw_backend::users::User; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -17,6 +18,13 @@ async fn main() -> std::io::Result<()> { .await .expect("Failed to connect to Redis!"); + // Auto create default account, if requested + if let Some(mail) = &AppConfig::get().unsecure_auto_login_email() { + User::create_or_update_user(mail, "Anonymous") + .await + .expect("Failed to create auto-login account!"); + } + log::info!( "Starting to listen on {} for {}", AppConfig::get().listen_address, @@ -40,6 +48,20 @@ async fn main() -> std::io::Result<()> { "/api/server/config", web::get().to(server_controller::config), ) + // Auth controller + .route( + "/api/auth/start_oidc", + web::get().to(auth_controller::start_oidc), + ) + .route( + "/api/auth/finish_oidc", + web::post().to(auth_controller::finish_oidc), + ) + .route("/api/auth/info", web::get().to(auth_controller::auth_info)) + .route( + "/api/auth/sign_out", + web::get().to(auth_controller::sign_out), + ) }) .workers(4) .bind(&AppConfig::get().listen_address)? diff --git a/matrixgw_backend/src/users.rs b/matrixgw_backend/src/users.rs new file mode 100644 index 0000000..2cb1e0a --- /dev/null +++ b/matrixgw_backend/src/users.rs @@ -0,0 +1,168 @@ +use crate::app_config::AppConfig; +use crate::utils::time_utils::time_secs; +use jwt_simple::reexports::serde_json; +use std::cmp::min; +use std::str::FromStr; + +/// Matrix Gateway user errors +#[derive(thiserror::Error, Debug)] +enum MatrixGWUserError { + #[error("Failed to load user metadata: {0}")] + LoadUserMetadata(std::io::Error), + #[error("Failed to decode user metadata: {0}")] + DecodeUserMetadata(serde_json::Error), + #[error("Failed to save user metadata: {0}")] + SaveUserMetadata(std::io::Error), + #[error("Failed to delete API token: {0}")] + DeleteToken(std::io::Error), + #[error("Failed to load API token: {0}")] + LoadApiToken(std::io::Error), + #[error("Failed to decode API token: {0}")] + DecodeApiToken(serde_json::Error), + #[error("API Token does not exists!")] + ApiTokenDoesNotExists, + #[error("Failed to save API token: {0}")] + SaveAPIToken(std::io::Error), +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +pub struct UserEmail(pub String); + +impl UserEmail { + pub fn is_valid(&self) -> bool { + mailchecker::is_valid(&self.0) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct APITokenID(pub uuid::Uuid); + +impl Default for APITokenID { + fn default() -> Self { + Self(uuid::Uuid::new_v4()) + } +} + +impl FromStr for APITokenID { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result { + Ok(Self(uuid::Uuid::from_str(s)?)) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct User { + pub email: UserEmail, + pub name: String, + pub time_create: u64, + pub last_login: u64, +} + +impl User { + /// Get a user by its mail + pub async fn get_by_mail(mail: &UserEmail) -> anyhow::Result { + let path = AppConfig::get().user_metadata_file_path(mail); + let data = std::fs::read_to_string(path).map_err(MatrixGWUserError::LoadUserMetadata)?; + Ok(serde_json::from_str(&data).map_err(MatrixGWUserError::DecodeUserMetadata)?) + } + + /// Update user metadata on disk + pub async fn write(&self) -> anyhow::Result<()> { + let path = AppConfig::get().user_metadata_file_path(&self.email); + std::fs::write(&path, serde_json::to_string(&self)?) + .map_err(MatrixGWUserError::SaveUserMetadata)?; + Ok(()) + } + + /// Create or update user information + pub async fn create_or_update_user(mail: &UserEmail, name: &str) -> anyhow::Result { + let storage_dir = AppConfig::get().user_directory(mail); + let mut user = if !storage_dir.exists() { + std::fs::create_dir_all(storage_dir)?; + + User { + email: mail.clone(), + name: name.to_string(), + time_create: time_secs(), + last_login: time_secs(), + } + } else { + Self::get_by_mail(mail).await? + }; + + // Update some user information + user.name = name.to_string(); + user.last_login = time_secs(); + user.write().await?; + + Ok(user) + } +} + +/// Single API client information +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct APIToken { + /// Token unique ID + pub id: APITokenID, + + /// Client description + pub description: String, + + /// Restricted API network for token + pub network: Option, + + /// Client secret + pub secret: String, + + /// Client creation time + pub created: u64, + + /// Client last usage time + pub last_used: u64, + + /// Read only access + pub read_only: bool, + + /// Token max inactivity + pub max_inactivity: u64, +} + +impl APIToken { + /// Get a token information + pub async fn load(email: &UserEmail, id: &APITokenID) -> anyhow::Result { + let token_file = AppConfig::get().user_api_token_metadata_file(email, id); + match token_file.exists() { + true => Ok(serde_json::from_str::( + &std::fs::read_to_string(&token_file).map_err(MatrixGWUserError::LoadApiToken)?, + ) + .map_err(MatrixGWUserError::DecodeApiToken)?), + false => Err(MatrixGWUserError::ApiTokenDoesNotExists.into()), + } + } + + /// Write this token information + pub async fn write(&self, mail: &UserEmail) -> anyhow::Result<()> { + let path = AppConfig::get().user_api_token_metadata_file(mail, &self.id); + std::fs::write(&path, serde_json::to_string(&self)?) + .map_err(MatrixGWUserError::SaveAPIToken)?; + Ok(()) + } + + /// Delete this token + pub async fn delete(self, email: &UserEmail) -> anyhow::Result<()> { + let token_file = AppConfig::get().user_api_token_metadata_file(email, &self.id); + std::fs::remove_file(&token_file).map_err(MatrixGWUserError::DeleteToken)?; + Ok(()) + } + + pub fn shall_update_time_used(&self) -> bool { + let refresh_interval = min(600, self.max_inactivity / 10); + + (self.last_used) < time_secs() - refresh_interval + } + + pub fn is_expired(&self) -> bool { + (self.last_used + self.max_inactivity) < time_secs() + } +} diff --git a/matrixgw_backend/src/utils/crypt_utils.rs b/matrixgw_backend/src/utils/crypt_utils.rs new file mode 100644 index 0000000..9a22fa9 --- /dev/null +++ b/matrixgw_backend/src/utils/crypt_utils.rs @@ -0,0 +1,6 @@ +use sha2::{Digest, Sha256}; + +/// Compute SHA256sum of a given string +pub fn sha256str(input: &str) -> String { + hex::encode(Sha256::digest(input.as_bytes())) +} diff --git a/matrixgw_backend/src/utils/mod.rs b/matrixgw_backend/src/utils/mod.rs new file mode 100644 index 0000000..ff5baec --- /dev/null +++ b/matrixgw_backend/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod crypt_utils; +pub mod rand_utils; +pub mod time_utils; diff --git a/matrixgw_backend/src/utils/rand_utils.rs b/matrixgw_backend/src/utils/rand_utils.rs new file mode 100644 index 0000000..aa40750 --- /dev/null +++ b/matrixgw_backend/src/utils/rand_utils.rs @@ -0,0 +1,6 @@ +use rand::distr::{Alphanumeric, SampleString}; + +/// Generate a random string of a given length +pub fn rand_string(len: usize) -> String { + Alphanumeric.sample_string(&mut rand::rng(), len) +} diff --git a/matrixgw_backend/src/utils/time_utils.rs b/matrixgw_backend/src/utils/time_utils.rs new file mode 100644 index 0000000..ac8c636 --- /dev/null +++ b/matrixgw_backend/src/utils/time_utils.rs @@ -0,0 +1,9 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get the current time since epoch +pub fn time_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +}