diff --git a/Cargo.lock b/Cargo.lock index 696cfc0..ffd758d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,18 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "askama" version = "0.12.1" @@ -489,6 +501,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.20.0" @@ -501,6 +519,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "basic-toml" version = "0.1.9" @@ -510,12 +534,29 @@ dependencies = [ "serde", ] +[[package]] +name = "binstring" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed79c2a8151273c70956b5e3cdfdc1ff6c1a8b9779ba59c6807d281b32ee2f86" + [[package]] name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -663,6 +704,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "coarsetime" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4252bf230cb600c19826a575b31c8c9c84c6f11acfab6dfcad2e941b10b6f8e2" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -696,6 +748,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-random" version = "0.1.18" @@ -716,6 +774,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -780,6 +844,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -791,6 +867,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4" + [[package]] name = "ctr" version = "0.9.2" @@ -800,6 +882,17 @@ dependencies = [ "cipher", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -851,6 +944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -875,6 +969,51 @@ dependencies = [ "const-random", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "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.15", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -929,6 +1068,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "flate2" version = "1.0.35" @@ -1066,6 +1215,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1075,8 +1225,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1110,6 +1262,17 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[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.26" @@ -1190,6 +1353,30 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3" + +[[package]] +name = "hmac-sha256" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a8575493d277c9092b988c780c94737fb9fd8651a1001e16bee3eccfc1baedb" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b3a0f572aa8389d325f5852b9e0a333a15b0f86ecccbb3fdb6e97cd86dc67c" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1570,6 +1757,46 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00e03c08ce71da10a3ad9267b963c03fc4234a56713d87648547b3fdda872a6" +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 2.0.11", + "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" @@ -1581,6 +1808,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" @@ -1664,7 +1894,9 @@ dependencies = [ "chrono", "clap", "env_logger", + "futures-util", "ipnet", + "jwt-simple", "lazy_static", "light-openid", "log", @@ -1791,6 +2023,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1806,6 +2055,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" @@ -1813,6 +2073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1890,6 +2151,30 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1919,6 +2204,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[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.1" @@ -1957,6 +2251,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.31" @@ -1990,6 +2305,15 @@ dependencies = [ "zerocopy 0.7.35", ] +[[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.93" @@ -2202,6 +2526,16 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.8" @@ -2217,6 +2551,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rsa" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rust-embed" version = "8.5.0" @@ -2418,6 +2773,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", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2528,6 +2897,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 = "slab" version = "0.4.9" @@ -2559,6 +2938,16 @@ 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.0" @@ -2583,6 +2972,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.15", + "hmac-sha256", + "hmac-sha512", + "rand 0.8.5", + "rsa", +] + [[package]] name = "syn" version = "2.0.96" @@ -3001,6 +3403,15 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" diff --git a/Cargo.toml b/Cargo.toml index b8e6588..92762d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,6 @@ askama = "0.12.1" urlencoding = "2.1.3" uuid = { version = "1.12.1", features = ["v4", "serde"] } ipnet = { version = "2.11.0", features = ["serde"] } -chrono = "0.4.39" \ No newline at end of file +chrono = "0.4.39" +futures-util = "0.3.31" +jwt-simple = { version = "0.12.11", default-features=false, features=["pure-rust"] } \ No newline at end of file diff --git a/assets/style.css b/assets/style.css index b893e25..f83653e 100644 --- a/assets/style.css +++ b/assets/style.css @@ -5,4 +5,8 @@ .body-content .card-header { font-weight: bold; +} + +#user_id_container { + margin: 20px auto; } \ No newline at end of file diff --git a/src/extractors/client_auth.rs b/src/extractors/client_auth.rs new file mode 100644 index 0000000..ab974d8 --- /dev/null +++ b/src/extractors/client_auth.rs @@ -0,0 +1,113 @@ +use crate::user::{APIClient, APIClientID, UserConfig, UserID}; +use actix_web::dev::Payload; +use actix_web::{FromRequest, HttpRequest}; +use jwt_simple::common::VerificationOptions; +use jwt_simple::prelude::{HS256Key, MACLike}; +use std::str::FromStr; + +pub struct APIClientAuth { + pub user: UserConfig, + client: APIClient, + payload: Option>, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +struct JWTClaims {} + +impl APIClientAuth { + async fn extract_auth(req: &HttpRequest) -> Result { + let Some(token) = req.headers().get("x-client-auth") else { + return Err(actix_web::error::ErrorBadRequest( + "Missing authentication 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!", + )); + } + }; + + let Some(kid) = metadata.key_id() else { + return Err(actix_web::error::ErrorBadRequest( + "Missing key id in request!", + )); + }; + + let Some((user_id, client_id)) = kid.split_once("#") else { + return Err(actix_web::error::ErrorBadRequest( + "Invalid key format (missing part)!", + )); + }; + + let (Ok(user_id), Ok(client_id)) = + (urlencoding::decode(user_id), urlencoding::decode(client_id)) + else { + return Err(actix_web::error::ErrorBadRequest( + "Invalid key format (decoding failed)!", + )); + }; + + // Fetch user + const USER_NOT_FOUND_ERROR: &str = "User not found!"; + let user = match UserConfig::load(&UserID(user_id.to_string()), false).await { + Ok(u) => u, + Err(e) => { + log::error!("Failed to get user information! {e}"); + return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); + } + }; + + // Find client + let Ok(client_id) = APIClientID::from_str(&client_id) else { + return Err(actix_web::error::ErrorBadRequest("Invalid token format!")); + }; + let Some(client) = user.find_client_by_id(&client_id) else { + log::error!("Client not found for user!"); + return Err(actix_web::error::ErrorForbidden(USER_NOT_FOUND_ERROR)); + }; + + // Decode JWT + let key = HS256Key::from_bytes(client.secret.as_bytes()); + let claims = + match key.verify_token::(jwt_token, Some(VerificationOptions::default())) { + Ok(t) => t, + Err(e) => { + log::error!("JWT validation failed! {e}"); + return Err(actix_web::error::ErrorForbidden("JWT validation failed!")); + } + }; + + // TODO : check timing + // TODO : check URI & verb + // TODO : handle payload + // TODO : check read only access + // TODO : update last use (if required) + // TODO : check for IP restriction + + Ok(Self { + client: client.clone(), + payload: None, + user, + }) + } +} + +impl FromRequest for APIClientAuth { + 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(); + Box::pin(async move { Self::extract_auth(&req).await }) + } +} diff --git a/src/extractors/mod.rs b/src/extractors/mod.rs new file mode 100644 index 0000000..f2e5226 --- /dev/null +++ b/src/extractors/mod.rs @@ -0,0 +1 @@ +pub mod client_auth; diff --git a/src/lib.rs b/src/lib.rs index d410f77..54143a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod app_config; pub mod constants; +pub mod extractors; pub mod server; pub mod user; pub mod utils; diff --git a/src/main.rs b/src/main.rs index b5d0005..294f4fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use actix_session::{storage::RedisSessionStore, SessionMiddleware}; use actix_web::cookie::Key; use actix_web::{web, App, HttpServer}; use matrix_gateway::app_config::AppConfig; -use matrix_gateway::server::web_ui; +use matrix_gateway::server::{api, web_ui}; use matrix_gateway::user::UserConfig; #[actix_web::main] @@ -41,9 +41,8 @@ async fn main() -> std::io::Result<()> { .route("/", web::post().to(web_ui::home)) .route("/oidc_cb", web::get().to(web_ui::oidc_cb)) .route("/sign_out", web::get().to(web_ui::sign_out)) - - // API routes - // TODO + // API routes + .route("/api/", web::get().to(api::api_home)) }) .bind(&AppConfig::get().listen_address)? .run() diff --git a/src/server/api.rs b/src/server/api.rs new file mode 100644 index 0000000..5f41922 --- /dev/null +++ b/src/server/api.rs @@ -0,0 +1,8 @@ +use crate::extractors::client_auth::APIClientAuth; +use crate::server::HttpResult; +use actix_web::HttpResponse; + +/// API Home route +pub async fn api_home(auth: APIClientAuth) -> HttpResult { + Ok(HttpResponse::Ok().body(format!("Welcome user {}!", auth.user.user_id.0))) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index a60ec5c..5fbdb77 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,6 +2,7 @@ use actix_web::http::StatusCode; use actix_web::{HttpResponse, ResponseError}; use std::error::Error; +pub mod api; pub mod web_ui; #[derive(thiserror::Error, Debug)] diff --git a/src/server/web_ui.rs b/src/server/web_ui.rs index ac6793e..9da87dc 100644 --- a/src/server/web_ui.rs +++ b/src/server/web_ui.rs @@ -1,7 +1,7 @@ use crate::app_config::AppConfig; use crate::constants::{STATE_KEY, USER_SESSION_KEY}; use crate::server::{HttpFailure, HttpResult}; -use crate::user::{APIClient, User, UserConfig, UserID}; +use crate::user::{APIClient, APIClientID, User, UserConfig, UserID}; use crate::utils; use actix_session::Session; use actix_web::{web, HttpResponse}; @@ -33,6 +33,7 @@ pub async fn static_file(path: web::Path) -> HttpResult { #[template(path = "index.html")] struct HomeTemplate { name: String, + user_id: UserID, matrix_token: String, clients: Vec, success_message: Option, @@ -55,7 +56,7 @@ pub struct FormRequest { readonly_client: Option, /// Delete a specified client id - delete_client_id: Option, + delete_client_id: Option, } /// Main route @@ -82,7 +83,7 @@ pub async fn home(session: Session, form_req: Option>) -> let mut error_message = None; // Retrieve user configuration - let mut config = UserConfig::load(&user.id) + let mut config = UserConfig::load(&user.id, true) .await .map_err(HttpFailure::FetchUserConfig)?; @@ -137,6 +138,7 @@ pub async fn home(session: Session, form_req: Option>) -> .body( HomeTemplate { name: user.name, + user_id: user.id, matrix_token: config.obfuscated_matrix_token(), clients: config.clients, success_message, diff --git a/src/user.rs b/src/user.rs index 56f60dd..5766b43 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,6 +1,7 @@ use s3::error::S3Error; use s3::request::ResponseData; use s3::{Bucket, BucketConfiguration}; +use std::str::FromStr; use thiserror::Error; use crate::app_config::AppConfig; @@ -29,11 +30,28 @@ pub struct User { pub email: String, } +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct APIClientID(pub uuid::Uuid); + +impl APIClientID { + pub fn generate() -> Self { + Self(uuid::Uuid::new_v4()) + } +} + +impl FromStr for APIClientID { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result { + Ok(Self(uuid::Uuid::from_str(s)?)) + } +} + /// Single API client information -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct APIClient { /// Client unique ID - pub id: uuid::Uuid, + pub id: APIClientID, /// Client description pub description: String, @@ -68,7 +86,7 @@ impl APIClient { /// Generate a new API client pub fn generate(description: String, network: Option) -> Self { Self { - id: uuid::Uuid::new_v4(), + id: APIClientID::generate(), description, network, secret: rand_str(TOKEN_LEN), @@ -137,15 +155,15 @@ impl UserConfig { } /// Get current user configuration - pub async fn load(user_id: &UserID) -> anyhow::Result { + pub async fn load(user_id: &UserID, allow_non_existing: bool) -> anyhow::Result { let res: Result = AppConfig::get() .s3_bucket()? .get_object(user_id.conf_path_in_bucket()) .await; - match res { - Ok(res) => Ok(serde_json::from_slice(res.as_slice())?), - Err(S3Error::HttpFailWithBody(404, _)) => { + match (res, allow_non_existing) { + (Ok(res), _) => Ok(serde_json::from_slice(res.as_slice())?), + (Err(S3Error::HttpFailWithBody(404, _)), true) => { log::warn!("User configuration does not exists, generating a new one..."); Ok(Self { user_id: user_id.clone(), @@ -155,7 +173,7 @@ impl UserConfig { clients: vec![], }) } - Err(e) => Err(UserError::FetchUserConfig(e).into()), + (Err(e), _) => Err(UserError::FetchUserConfig(e).into()), } } @@ -188,4 +206,9 @@ impl UserConfig { }) .collect() } + + /// Find a client by its id + pub fn find_client_by_id(&self, id: &APIClientID) -> Option<&APIClient> { + self.clients.iter().find(|c| &c.id == id) + } } diff --git a/templates/index.html b/templates/index.html index 7857fcc..d5bd290 100644 --- a/templates/index.html +++ b/templates/index.html @@ -47,6 +47,9 @@ {% endif %} + +
Current user ID: {{ user_id.0 }}
+
Registered clients
@@ -67,7 +70,7 @@ {% for client in clients %} - {{ client.id }} + {{ client.id.0 }} {{ client.description }} {% if client.readonly_client %} @@ -86,7 +89,7 @@ {{ client.fmt_created() }} {{ client.fmt_used() }} -