From 17ad9271877cf8bca7c04d83af825ca7f4b67af4 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 3 Oct 2023 17:04:35 -0400 Subject: [PATCH] Validate the session token A previous commit added authentication token checks. Auth tokens are replaced with session tokens, which can (and should) expire. This commit validates sessions, which now allows access to gated operations. --- .gitignore | 4 ++- Cargo.lock | 51 ++++++++++++++++++++++++++++++++--- file-service/Cargo.toml | 5 ++-- file-service/src/handlers.rs | 19 ++++++++++--- file-service/src/main.rs | 34 +++++++++++++++++------ file-service/src/store/mod.rs | 15 ++++++----- 6 files changed, 104 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 021583e..4539abe 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules dist result *.tgz -file-service/auth.sqlite +file-service/*.sqlite +file-service/*.sqlite-shm +file-service/*.sqlite-wal diff --git a/Cargo.lock b/Cargo.lock index 39219d7..519c7fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,7 +416,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.37", ] [[package]] @@ -471,6 +471,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "time 0.3.29", + "version_check 0.9.4", +] + [[package]] name = "cool_asserts" version = "2.0.3" @@ -661,6 +671,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "digest" version = "0.10.7" @@ -842,6 +858,7 @@ dependencies = [ "bytes", "chrono", "clap 4.4.6", + "cookie", "cool_asserts", "futures-util", "hex-string", @@ -1744,7 +1761,7 @@ dependencies = [ "log 0.3.9", "mime 0.2.6", "num_cpus", - "time", + "time 0.1.45", "traitobject", "typeable", "unicase 1.4.2", @@ -2178,7 +2195,7 @@ checksum = "6c9172cb4c2f6c52117e25570983edcbb322f130b1031ae5d5d6b1abe7eeb493" dependencies = [ "iron", "log 0.3.9", - "time", + "time 0.1.45", ] [[package]] @@ -3942,6 +3959,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa", + "serde 1.0.188", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "tinystr" version = "0.7.4" diff --git a/file-service/Cargo.toml b/file-service/Cargo.toml index 0766c1e..8238be0 100644 --- a/file-service/Cargo.toml +++ b/file-service/Cargo.toml @@ -25,9 +25,12 @@ required-features = [ "auth-cli" ] [target.auth-cli.dependencies] [dependencies] +base64ct = { version = "1", features = [ "alloc" ] } build_html = { version = "2" } bytes = { version = "1" } chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4", features = [ "derive" ], optional = true } +cookie = { version = "0.17" } futures-util = { version = "0.3" } hex-string = "0.1.0" http = { version = "0.2" } @@ -45,8 +48,6 @@ thiserror = "1.0.20" tokio = { version = "1", features = [ "full" ] } uuid = { version = "0.4", features = [ "serde", "v4" ] } warp = { version = "0.3" } -base64ct = { version = "1", features = [ "alloc" ] } -clap = { version = "4", features = [ "derive" ], optional = true } [dev-dependencies] cool_asserts = { version = "2" } diff --git a/file-service/src/handlers.rs b/file-service/src/handlers.rs index 824d1da..6f3fdc7 100644 --- a/file-service/src/handlers.rs +++ b/file-service/src/handlers.rs @@ -10,9 +10,9 @@ pub async fn handle_index( token: Option, ) -> Result, Error> { match token { - Some(token) => match app.auth_session(token).await { + Some(token) => match app.validate_session(token).await { Ok(_) => render_gallery_page(app).await, - Err(err) => render_auth_page(Some(format!("authentication refused: {:?}", err))), + Err(err) => render_auth_page(Some(format!("session expired: {:?}", err))), }, None => render_auth_page(None), } @@ -76,10 +76,21 @@ pub async fn handle_auth( ) -> Result, Error> { match form.get("token") { Some(token) => match app - .auth_token(AuthToken::from(AuthToken::from(token.clone()))) + .authenticate(AuthToken::from(AuthToken::from(token.clone()))) .await { - Ok(_session_token) => render_gallery_page(app).await, + Ok(Some(session_token)) => Response::builder() + .header("location", "/") + .header( + "set-cookie", + format!( + "session={}; Secure; HttpOnly; SameSite=Strict", + session_token.to_string() + ), + ) + .status(StatusCode::SEE_OTHER) + .body("".to_owned()), + Ok(None) => render_auth_page(Some(format!("no user found"))), Err(_) => render_auth_page(Some(format!("invalid auth token"))), }, None => render_auth_page(Some(format!("no token available"))), diff --git a/file-service/src/main.rs b/file-service/src/main.rs index 7f5a3db..6918cbc 100644 --- a/file-service/src/main.rs +++ b/file-service/src/main.rs @@ -6,9 +6,10 @@ use http::status::StatusCode; // use mustache::{compile_path, Template}; // use orizentic::{Permissions, ResourceName, Secret}; use bytes::Buf; +use cookie::Cookie; use futures_util::StreamExt; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, convert::Infallible, io::Read, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -126,12 +127,15 @@ impl App { } } - pub async fn auth_token(&self, token: AuthToken) -> Result, AuthError> { - self.authdb.read().await.auth_token(token).await + pub async fn authenticate(&self, token: AuthToken) -> Result, AuthError> { + self.authdb.read().await.authenticate(token).await } - pub async fn auth_session(&self, token: SessionToken) -> Result, AuthError> { - self.authdb.read().await.auth_session(token).await + pub async fn validate_session( + &self, + token: SessionToken, + ) -> Result, AuthError> { + self.authdb.read().await.validate_session(token).await } pub async fn list_files(&self) -> Result, ReadFileError> { @@ -159,9 +163,23 @@ fn maybe_with_session() -> impl Filter,), Error { warp::any() .and(warp::header::optional::("cookie")) - .map(|cookies| { - println!("cookies retrieved: {:?}", cookies); - None + .map(|cookies| match cookies { + Some(cookies) => { + let c = Cookie::split_parse(cookies) + .collect::, cookie::ParseError>>(); + match c { + Ok(cookies) => { + for c in cookies { + if c.name() == "session" { + return Some(SessionToken::from(c.value())); + } + } + None + } + Err(_) => None, + } + } + None => None, }) } diff --git a/file-service/src/store/mod.rs b/file-service/src/store/mod.rs index b5a1eeb..119f02a 100644 --- a/file-service/src/store/mod.rs +++ b/file-service/src/store/mod.rs @@ -278,7 +278,7 @@ impl AuthDB { Ok(usernames) } - pub async fn auth_token(&self, token: AuthToken) -> Result, AuthError> { + pub async fn authenticate(&self, token: AuthToken) -> Result, AuthError> { let results = sqlx::query("SELECT * FROM users WHERE token = $1") .bind(token.to_string()) .fetch_all(&self.pool) @@ -308,7 +308,10 @@ impl AuthDB { Ok(Some(SessionToken::from(session_token))) } - pub async fn auth_session(&self, token: SessionToken) -> Result, AuthError> { + pub async fn validate_session( + &self, + token: SessionToken, + ) -> Result, AuthError> { let rows = sqlx::query( "SELECT users.username FROM sessions INNER JOIN users ON sessions.user_id = users.id WHERE sessions.token = $1", ) @@ -498,7 +501,7 @@ mod authdb_test { let token = AuthToken::from("0000000000"); - assert_matches!(db.auth_token(token).await, Ok(None)); + assert_matches!(db.authenticate(token).await, Ok(None)); } #[tokio::test] @@ -511,7 +514,7 @@ mod authdb_test { .await .expect("user to be created"); - assert_matches!(db.auth_token(token).await, Ok(_)); + assert_matches!(db.authenticate(token).await, Ok(_)); } #[tokio::test] @@ -524,13 +527,13 @@ mod authdb_test { .await .expect("user to be created"); let session = db - .auth_token(token) + .authenticate(token) .await .expect("token authentication should succeed") .expect("session token should be found"); assert_matches!( - db.auth_session(session).await, + db.validate_session(session).await, Ok(Some(username)) => { assert_eq!(username, Username::from("savanni")); });