diff --git a/Cargo.lock b/Cargo.lock index 9b45652..63b9557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,166 +17,12 @@ version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-channel" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-mutex", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" -dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", -] - -[[package]] -name = "async-lock" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-redis-session" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba82ce101e6cde598074604ef4a882bdd6b3a283baff446ae73ae2727c242452" -dependencies = [ - "async-session", - "redis 0.20.2", -] - -[[package]] -name = "async-session" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da4ce523b4e2ebaaf330746761df23a465b951a83d84bbce4233dabedae630" -dependencies = [ - "anyhow", - "async-lock", - "async-trait", - "base64", - "bincode", - "blake3", - "chrono", - "hmac", - "log", - "rand", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "async-std" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" -dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "num_cpus", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" - [[package]] name = "async-trait" version = "0.1.52" @@ -208,12 +54,6 @@ dependencies = [ "autocfg 1.1.0", ] -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - [[package]] name = "auto_impl" version = "0.5.0" @@ -326,7 +166,7 @@ checksum = "c440295545cb69b3cec992ae8844fbb1de1c84f2f90248438af287e14bb09bde" dependencies = [ "async-trait", "bb8", - "redis 0.21.5", + "redis", ] [[package]] @@ -356,21 +196,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake3" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "cc", - "cfg-if 0.1.10", - "constant_time_eq", - "crypto-mac 0.8.0", - "digest", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -387,20 +212,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - [[package]] name = "bumpalo" version = "3.9.1" @@ -428,12 +239,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - [[package]] name = "cc" version = "1.0.72" @@ -504,15 +309,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -535,12 +331,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "cookie" version = "0.16.0" @@ -576,16 +366,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" -dependencies = [ - "cfg-if 1.0.0", - "lazy_static", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -616,16 +396,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -636,16 +406,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ctor" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "der" version = "0.4.5" @@ -794,7 +554,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f15e1a2a54bc6bc3f8ea94afafbb374264f8322fcacdae06fefda80a206739ac" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", "bytes", "ecdsa", "elliptic-curve 0.11.12", @@ -846,21 +606,6 @@ dependencies = [ "ws_stream_wasm", ] -[[package]] -name = "event-listener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - -[[package]] -name = "fastrand" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] - [[package]] name = "ff" version = "0.10.1" @@ -967,21 +712,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-macro" version = "0.3.21" @@ -1052,18 +782,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gloo-timers" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.10.0" @@ -1146,7 +864,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac", "digest", ] @@ -1368,15 +1086,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1414,7 +1123,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", - "value-bag", ] [[package]] @@ -1697,7 +1405,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" dependencies = [ - "arrayvec 0.7.2", + "arrayvec", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", @@ -1717,12 +1425,6 @@ dependencies = [ "syn", ] -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - [[package]] name = "parking_lot" version = "0.11.2" @@ -1911,19 +1613,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -2054,27 +1743,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "redis" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4f0ceb2ec0dd769483ecd283f6615aa83dcd0be556d5294c6e659caefe7cc54" -dependencies = [ - "async-std", - "async-trait", - "bytes", - "combine", - "dtoa", - "futures-util", - "itoa 0.4.8", - "percent-encoding", - "pin-project-lite", - "sha1", - "tokio", - "tokio-util", - "url", -] - [[package]] name = "redis" version = "0.21.5" @@ -2447,21 +2115,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.9.9" @@ -2543,8 +2196,6 @@ name = "siwe-oidc" version = "0.1.0" dependencies = [ "anyhow", - "async-redis-session", - "async-session", "async-trait", "axum", "bb8-redis", @@ -3108,28 +2759,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.0.0-alpha.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "want" version = "0.3.0" @@ -3289,15 +2924,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 6969310..41d1283 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ ethers-providers = "0.6.2" lazy_static = "1.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -async-session = "3.0.0" axum = { version = "0.4.3", features = ["headers"] } # axum-debug = "0.3.2" chrono = "0.4.19" @@ -44,7 +43,6 @@ tokio = { version = "1.14.0", features = ["full"] } tower-http = { version = "0.2.0", features = ["fs", "trace", "cors"] } tracing-subscriber = { version = "0.3.2", features = ["env-filter"] } bb8-redis = "0.10.1" -async-redis-session = "0.2.2" uuid = { version = "0.8", features = ["serde", "v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/src/axum_lib.rs b/src/axum_lib.rs index bf839bf..84ad128 100644 --- a/src/axum_lib.rs +++ b/src/axum_lib.rs @@ -1,5 +1,4 @@ use anyhow::{anyhow, Result}; -use async_redis_session::RedisSessionStore; use axum::{ extract::{self, Extension, Form, Path, Query, TypedHeader}, http::{ @@ -22,7 +21,7 @@ use headers::{ }; use openidconnect::core::{ CoreClientMetadata, CoreClientRegistrationResponse, CoreJsonWebKeySet, CoreProviderMetadata, - CoreResponseType, CoreTokenResponse, CoreUserInfoClaims, CoreUserInfoJsonWebToken, + CoreTokenResponse, CoreUserInfoClaims, CoreUserInfoJsonWebToken, }; use rand::rngs::OsRng; use rsa::{ @@ -38,7 +37,6 @@ use tracing::info; use super::config; use super::oidc::{self, CustomError}; -use super::session::*; use ::siwe_oidc::db::*; impl IntoResponse for CustomError { @@ -82,14 +80,6 @@ async fn provider_metadata( Ok(oidc::metadata(config.base_url)?.into()) } -// TODO should check Authorization header -// Actually, client secret can be -// 1. in the POST (currently supported) [x] -// 2. Authorization header [x] -// 3. JWT [ ] -// 4. signed JWT [ ] -// according to Keycloak - async fn token( Form(form): Form, bearer: Option>>, @@ -116,43 +106,16 @@ async fn token( Ok(token_response.into()) } -// TODO handle `registration` parameter async fn authorize( - session: UserSessionFromSession, Query(params): Query, Extension(redis_client): Extension, ) -> Result<(HeaderMap, Redirect), CustomError> { - let (nonce, headers) = match session { - UserSessionFromSession::Found(nonce) => (nonce, HeaderMap::new()), - UserSessionFromSession::Invalid(cookie) => { - let mut headers = HeaderMap::new(); - headers.insert(header::SET_COOKIE, cookie); - return Ok(( - headers, - Redirect::to( - format!( -"/authorize?client_id={}&redirect_uri={}&scope={}&response_type={}&state={}&client_id={}{}", -¶ms.client_id, -¶ms.redirect_uri.to_string(), -¶ms.scope.to_string(), -¶ms.response_type.unwrap_or(CoreResponseType::Code).as_ref(), -¶ms.state.unwrap_or_default(), -¶ms.client_id, -¶ms.nonce.map(|n| format!("&nonce={}", n.secret())).unwrap_or_default() -) - .parse() - .map_err(|e| anyhow!("Could not parse URI: {}", e))?, - ), - )); - } - UserSessionFromSession::Created { header, nonce } => { - let mut headers = HeaderMap::new(); - headers.insert(header::SET_COOKIE, header); - (nonce, headers) - } - }; - - let url = oidc::authorize(params, nonce, &redis_client).await?; + let (url, session_cookie) = oidc::authorize(params, &redis_client).await?; + let mut headers = HeaderMap::new(); + headers.insert( + header::SET_COOKIE, + session_cookie.to_string().parse().unwrap(), + ); Ok(( headers, Redirect::to( @@ -164,58 +127,16 @@ async fn authorize( } async fn sign_in( - session: UserSessionFromSession, Query(params): Query, TypedHeader(cookies): TypedHeader, Extension(redis_client): Extension, -) -> Result<(HeaderMap, Redirect), CustomError> { - let (nonce, headers) = match session { - UserSessionFromSession::Found(nonce) => (nonce, HeaderMap::new()), - UserSessionFromSession::Invalid(header) => { - let mut headers = HeaderMap::new(); - headers.insert(header::SET_COOKIE, header); - return Ok(( - headers, - Redirect::to( - format!( - "/authorize?client_id={}&redirect_uri={}&scope=openid&response_type=code&state={}", - ¶ms.client_id.clone(), - ¶ms.redirect_uri.to_string(), -¶ms.state, -) - .parse() - .map_err(|e| anyhow!("Could not parse URI: {}", e))?, - ), - )); - } - UserSessionFromSession::Created { .. } => { - return Ok(( - HeaderMap::new(), - Redirect::to( - format!( - "/authorize?client_id={}&redirect_uri={}&scope=openid&response_type=code&state={}", - ¶ms.client_id.clone(), - ¶ms.redirect_uri.to_string(), - ¶ms.state, - ) - .parse() - .map_err(|e| anyhow!("Could not parse URI: {}", e))?, - ), - )) - } - }; - - let url = oidc::sign_in(params, Some(nonce), cookies, &redis_client).await?; - - Ok(( - headers, - Redirect::to( - url.as_str() - .parse() - .map_err(|e| anyhow!("Could not parse URI: {}", e))?, - ), +) -> Result { + let url = oidc::sign_in(params, cookies, &redis_client).await?; + Ok(Redirect::to( + url.as_str() + .parse() + .map_err(|e| anyhow!("Could not parse URI: {}", e))?, )) - // TODO clear session } async fn register( @@ -434,11 +355,6 @@ pub async fn main() { .layer(AddExtensionLayer::new(private_key)) .layer(AddExtensionLayer::new(config.clone())) .layer(AddExtensionLayer::new(redis_client)) - .layer(AddExtensionLayer::new( - RedisSessionStore::new(config.redis_url.clone()) - .unwrap() - .with_prefix("async-sessions/"), - )) .layer(TraceLayer::new_for_http()); let addr = SocketAddr::from((config.address, config.port)); diff --git a/src/db/cf.rs b/src/db/cf.rs index c4fff58..a8718e8 100644 --- a/src/db/cf.rs +++ b/src/db/cf.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -// use cached::{stores::TimedCache, Cached}; use chrono::{DateTime, Duration, Utc}; use matchit::Node; use std::collections::HashMap; @@ -109,7 +108,6 @@ impl DBClient for CFClient { .map_err(|e| anyhow!("Failed to serialize client entry: {}", e))?, ) .map_err(|e| anyhow!("Failed to build KV put: {}", e))? - // TODO put some sort of expiration for dynamic registration .execute() .await .map_err(|e| anyhow!("Failed to put KV: {}", e))?; @@ -202,4 +200,32 @@ impl DBClient for CFClient { code => Err(anyhow!("Error fetching from Durable Object: {}", code)), } } + + async fn set_session(&self, id: String, entry: SessionEntry) -> Result<()> { + self.ctx + .kv(KV_NAMESPACE) + .map_err(|e| anyhow!("Failed to get KV store: {}", e))? + .put( + &format!("{}/{}", KV_SESSION_PREFIX, id), + serde_json::to_string(&entry) + .map_err(|e| anyhow!("Failed to serialize client entry: {}", e))?, + ) + .map_err(|e| anyhow!("Failed to build KV put: {}", e))? + .expiration_ttl(SESSION_LIFETIME) + .execute() + .await + .map_err(|e| anyhow!("Failed to put KV: {}", e))?; + Ok(()) + } + + async fn get_session(&self, id: String) -> Result> { + Ok(self + .ctx + .kv(KV_NAMESPACE) + .map_err(|e| anyhow!("Failed to get KV store: {}", e))? + .get(&format!("{}/{}", KV_SESSION_PREFIX, id)) + .json() + .await + .map_err(|e| anyhow!("Failed to get KV: {}", e))?) + } } diff --git a/src/db/mod.rs b/src/db/mod.rs index a80f43b..7ff3591 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -15,7 +15,10 @@ mod cf; pub use cf::CFClient; const KV_CLIENT_PREFIX: &str = "clients"; +const KV_SESSION_PREFIX: &str = "sessions"; pub const ENTRY_LIFETIME: usize = 30; +pub const SESSION_LIFETIME: u64 = 300; // 5min +pub const SESSION_COOKIE_NAME: &str = "session"; #[derive(Clone, Serialize, Deserialize)] pub struct CodeEntry { @@ -33,6 +36,14 @@ pub struct ClientEntry { pub access_token: Option, } +#[derive(Clone, Serialize, Deserialize)] +pub struct SessionEntry { + pub siwe_nonce: String, + pub oidc_nonce: Option, + pub secret: String, + pub signin_count: u64, +} + // Using a trait to easily pass async functions with async_trait #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -42,4 +53,6 @@ pub trait DBClient { async fn delete_client(&self, client_id: String) -> Result<()>; async fn set_code(&self, code: String, code_entry: CodeEntry) -> Result<()>; async fn get_code(&self, code: String) -> Result>; + async fn set_session(&self, id: String, entry: SessionEntry) -> Result<()>; + async fn get_session(&self, id: String) -> Result>; } diff --git a/src/db/redis.rs b/src/db/redis.rs index 7912022..1854fce 100644 --- a/src/db/redis.rs +++ b/src/db/redis.rs @@ -98,4 +98,40 @@ impl DBClient for RedisClient { .map_err(|e| anyhow!("Failed to deserialize code: {}", e))?; Ok(Some(code_entry)) } + + async fn set_session(&self, id: String, entry: SessionEntry) -> Result<()> { + let mut conn = self + .pool + .get() + .await + .map_err(|e| anyhow!("Failed to get connection to database: {}", e))?; + + conn.set_ex( + format!("{}/{}", KV_SESSION_PREFIX, id), + serde_json::to_string(&entry) + .map_err(|e| anyhow!("Failed to serialize session entry: {}", e))?, + SESSION_LIFETIME.try_into().unwrap(), + ) + .await + .map_err(|e| anyhow!("Failed to set kv: {}", e))?; + Ok(()) + } + + async fn get_session(&self, id: String) -> Result> { + let mut conn = self + .pool + .get() + .await + .map_err(|e| anyhow!("Failed to get connection to database: {}", e))?; + let entry: Option = conn + .get(format!("{}/{}", KV_SESSION_PREFIX, id)) + .await + .map_err(|e| anyhow!("Failed to get kv: {}", e))?; + if let Some(e) = entry { + Ok(serde_json::from_str(&e) + .map_err(|e| anyhow!("Failed to deserialize session entry: {}", e))?) + } else { + Ok(None) + } + } } diff --git a/src/main.rs b/src/main.rs index 600f9a2..fa29079 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,6 @@ mod config; #[cfg(not(target_arch = "wasm32"))] mod oidc; #[cfg(not(target_arch = "wasm32"))] -mod session; -#[cfg(not(target_arch = "wasm32"))] use axum_lib::main as axum_main; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/oidc.rs b/src/oidc.rs index 44bc7d9..437d5d6 100644 --- a/src/oidc.rs +++ b/src/oidc.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Result}; use chrono::{Duration, Utc}; +use cookie::Cookie; use ethers_core::{types::H160, utils::to_checksum}; use headers::{self, authorization::Bearer}; use hex::FromHex; @@ -315,9 +316,8 @@ pub struct AuthorizeParams { pub async fn authorize( params: AuthorizeParams, - nonce: String, db_client: &DBClientType, -) -> Result { +) -> Result<(String, Box>), CustomError> { let client_entry = db_client .get_client(params.client_id.clone()) .await @@ -328,6 +328,12 @@ pub async fn authorize( )); } + let nonce: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect(); + let mut r_u = params.redirect_uri.clone().url().clone(); r_u.set_query(None); let mut r_us: Vec = client_entry @@ -397,15 +403,45 @@ pub async fn authorize( } } + let session_id = Uuid::new_v4(); + let session_secret: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect(); + db_client + .set_session( + session_id.to_string(), + SessionEntry { + siwe_nonce: nonce.clone(), + oidc_nonce: params.nonce.clone(), + secret: session_secret.clone(), + signin_count: 0, + }, + ) + .await?; + let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session_id.to_string()) + // .domain(base) + // .path("/") + .secure(true) + .http_only(true) + .max_age(cookie::time::Duration::seconds( + SESSION_LIFETIME.try_into().unwrap(), + )) + .finish(); + let domain = params.redirect_uri.url().host().unwrap(); let oidc_nonce_param = if let Some(n) = ¶ms.nonce { format!("&oidc_nonce={}", n.secret()) } else { "".to_string() }; - Ok(format!( - "/?nonce={}&domain={}&redirect_uri={}&state={}&client_id={}{}", - nonce, domain, *params.redirect_uri, state, params.client_id, oidc_nonce_param + Ok(( + format!( + "/?nonce={}&domain={}&redirect_uri={}&state={}&client_id={}{}", + nonce, domain, *params.redirect_uri, state, params.client_id, oidc_nonce_param + ), + Box::new(session_cookie), )) } @@ -467,10 +503,29 @@ pub struct SignInParams { pub async fn sign_in( params: SignInParams, - expected_nonce: Option, + // cookies_header: String, cookies: headers::Cookie, db_client: &DBClientType, ) -> Result { + // TODO redirect on session errors + let session_id = if let Some(c) = cookies.get(SESSION_COOKIE_NAME) { + c + } else { + return Err(CustomError::BadRequest( + "Session cookie not found".to_string(), + )); + }; + let session_entry = if let Some(e) = db_client.get_session(session_id.to_string()).await? { + e + } else { + return Err(CustomError::BadRequest("Session not found".to_string())); + }; + if session_entry.signin_count > 0 { + return Err(CustomError::BadRequest( + "Session has already logged in".to_string(), + )); + } + let siwe_cookie: SiweCookie = match cookies.get(SIWE_COOKIE_KEY) { Some(c) => serde_json::from_str( &decode(c).map_err(|e| anyhow!("Could not decode siwe cookie: {}", e))?, @@ -508,7 +563,7 @@ pub async fn sign_in( if domain.to_string() != *siwe_cookie.message.resources.get(0).unwrap().to_string() { return Err(anyhow!("Conflicting domains in message and redirect").into()); } - if expected_nonce.is_some() && expected_nonce.unwrap() != siwe_cookie.message.nonce { + if session_entry.siwe_nonce != siwe_cookie.message.nonce { return Err(anyhow!("Conflicting nonces in message and session").into()); } @@ -520,6 +575,12 @@ pub async fn sign_in( auth_time: Utc::now(), }; + let mut new_session_entry = session_entry.clone(); + new_session_entry.signin_count += 1; + db_client + .set_session(session_id.to_string(), new_session_entry) + .await?; + let code = Uuid::new_v4(); db_client.set_code(code.to_string(), code_entry).await?; diff --git a/src/session.rs b/src/session.rs deleted file mode 100644 index 3b3dada..0000000 --- a/src/session.rs +++ /dev/null @@ -1,119 +0,0 @@ -use async_redis_session::RedisSessionStore; -use async_session::{Session, SessionStore as _}; -use axum::{ - async_trait, - extract::{Extension, FromRequest, RequestParts}, - http::{self, header::HeaderValue, StatusCode}, -}; -use cookie::Cookie; -use rand::{distributions::Alphanumeric, Rng}; -use serde::{Deserialize, Serialize}; -use tracing::debug; -use uuid::Uuid; - -const SESSION_COOKIE_NAME: &str = "session"; -const SESSION_KEY: &str = "user_session"; - -pub enum UserSessionFromSession { - Found(String), - Created { header: HeaderValue, nonce: String }, - Invalid(HeaderValue), -} - -#[async_trait] -impl FromRequest for UserSessionFromSession -where - B: Send, -{ - type Rejection = (StatusCode, String); - - async fn from_request(req: &mut RequestParts) -> Result { - let Extension(store) = match Extension::::from_request(req).await { - Ok(s) => s, - Err(e) => { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("`MemoryStore` extension missing: {}", e), - )) - } - }; - - let headers = if let Some(h) = req.headers() { - h - } else { - return Err(( - StatusCode::INTERNAL_SERVER_ERROR, - "other extractor taken headers".to_string(), - )); - }; - - let session_cookie: Cookie = if let Some(session_cookie) = headers - .get(http::header::COOKIE) - .and_then(|value| value.to_str().ok()) - .map(|header| { - header - .split(';') - .map(|cookie| Cookie::parse(cookie).ok()) - .find(|cookie| { - cookie.is_some() && cookie.as_ref().unwrap().name() == SESSION_COOKIE_NAME - }) - }) - .flatten() - .flatten() - { - session_cookie - } else { - let user_session = UserSession::new(); - let mut session = Session::new(); - session.insert(SESSION_KEY, user_session.clone()).unwrap(); - let cookie = store.store_session(session).await.unwrap().unwrap(); - - return Ok(Self::Created { - header: Cookie::new(SESSION_COOKIE_NAME, cookie) - .to_string() - .parse() - .unwrap(), - nonce: user_session.nonce, - }); - }; - - let session = match store.load_session(session_cookie.value().to_string()).await { - Ok(Some(s)) => s, - _ => { - debug!("Could not load session"); - let mut cookie = session_cookie.clone(); - cookie.make_removal(); - return Ok(Self::Invalid(cookie.to_string().parse().unwrap())); - } - }; - let user_session = if let Some(user_session) = session.get::(SESSION_KEY) { - user_session - } else { - debug!("No `user_session` found in session"); - let mut cookie = session_cookie.clone(); - cookie.make_removal(); - return Ok(Self::Invalid(cookie.to_string().parse().unwrap())); - }; - - Ok(Self::Found(user_session.nonce)) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct UserSession { - id: Uuid, - nonce: String, -} - -impl UserSession { - fn new() -> Self { - Self { - id: Uuid::new_v4(), - nonce: rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(16) - .map(char::from) - .collect(), - } - } -} diff --git a/src/worker_lib.rs b/src/worker_lib.rs index 86dfa6b..fec499a 100644 --- a/src/worker_lib.rs +++ b/src/worker_lib.rs @@ -4,7 +4,6 @@ use headers::{ authorization::{Basic, Bearer, Credentials}, Authorization, ContentType, Header, HeaderValue, }; -use rand::{distributions::Alphanumeric, Rng}; use rsa::{pkcs1::FromRsaPrivateKey, RsaPrivateKey}; use worker::*; @@ -197,7 +196,6 @@ pub async fn main(req: Request, env: Env) -> Result { } .and_then(|r| r.with_cors(&get_cors())) }) - // TODO add browser session .get_async(oidc::AUTHORIZE_PATH, |req, ctx| async move { let base_url: Url = ctx.var(BASE_URL_KEY)?.to_string().parse().unwrap(); let url = req.url()?; @@ -206,15 +204,18 @@ pub async fn main(req: Request, env: Env) -> Result { Ok(p) => p, Err(_) => return CustomError::BadRequest("Bad query params".to_string()).into(), }; - let nonce = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(16) - .map(char::from) - .collect(); let url = req.url()?; let db_client = CFClient { ctx, url }; - match oidc::authorize(params, nonce, &db_client).await { - Ok(url) => Response::redirect(base_url.join(&url).unwrap()), + match oidc::authorize(params, &db_client).await { + Ok((url, session_cookie)) => { + Response::redirect(base_url.join(&url).unwrap()).map(|r| { + let mut headers = r.headers().clone(); + headers + .set("set-cookie", &session_cookie.to_string()) + .unwrap(); + r.with_headers(headers) + }) + } Err(e) => match e { CustomError::Redirect(url) => { CustomError::Redirect(base_url.join(&url).unwrap().to_string()) @@ -320,7 +321,7 @@ pub async fn main(req: Request, env: Env) -> Result { } let url = req.url()?; let db_client = CFClient { ctx, url }; - match oidc::sign_in(params, None, cookies.unwrap(), &db_client).await { + match oidc::sign_in(params, cookies.unwrap(), &db_client).await { Ok(url) => Response::redirect(url), Err(e) => e.into(), }