From 2e62eef85be1e85e4d618488ad7a8b5e841ff281 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 7 Apr 2026 00:43:21 -0400 Subject: [PATCH] Bt cleanup web (#1313) --- .../http_user_session_payload.rs | 64 +++++++++++++++++++ .../http_user_session_payload_error.rs | 8 +++ .../http_user_session_payload_signer.rs | 10 +-- .../web/storyteller_web/src/api_doc_cli.rs | 1 - .../web/storyteller_web/src/api_doc_server.rs | 1 - .../src/error/internal_error.rs | 37 ----------- .../web/storyteller_web/src/error/mod.rs | 1 - .../service/web/storyteller_web/src/main.rs | 1 - 8 files changed, 73 insertions(+), 50 deletions(-) delete mode 100644 crates/service/web/storyteller_web/src/error/internal_error.rs delete mode 100644 crates/service/web/storyteller_web/src/error/mod.rs diff --git a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload.rs b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload.rs index 464859127b..151929b900 100644 --- a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload.rs +++ b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload.rs @@ -1,3 +1,6 @@ +use std::collections::BTreeMap; + +use crate::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError; #[derive(Clone)] pub struct HttpUserSessionPayload { @@ -8,3 +11,64 @@ pub struct HttpUserSessionPayload { /// Version 1 cookies do not have a user token, hence it is optional. pub maybe_user_token: Option, } + +impl HttpUserSessionPayload { + pub fn from_map( + map: BTreeMap, + ) -> Result { + let session_token = map + .get("session_token") + .ok_or(HttpUserSessionPayloadError::MissingField("session_token"))? + .clone(); + + let maybe_user_token = map.get("user_token").map(|t| t.to_string()); + + Ok(Self { + session_token, + maybe_user_token, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn map_of(pairs: &[(&str, &str)]) -> BTreeMap { + pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect() + } + + #[test] + fn from_map_with_session_and_user_tokens() { + let payload = HttpUserSessionPayload::from_map(map_of(&[ + ("session_token", "ex_session_token"), + ("user_token", "ex_user_token"), + ("version", "3"), + ])).unwrap(); + + assert_eq!(payload.session_token, "ex_session_token"); + assert_eq!(payload.maybe_user_token.as_deref(), Some("ex_user_token")); + } + + #[test] + fn from_map_without_user_token_is_ok() { + let payload = HttpUserSessionPayload::from_map(map_of(&[ + ("session_token", "ex_session_token"), + ])).unwrap(); + + assert_eq!(payload.session_token, "ex_session_token"); + assert!(payload.maybe_user_token.is_none()); + } + + #[test] + fn from_map_missing_session_token_errors() { + let result = HttpUserSessionPayload::from_map(map_of(&[ + ("user_token", "ex_user_token"), + ])); + + match result.err().expect("expected error") { + HttpUserSessionPayloadError::MissingField("session_token") => {} + other => panic!("expected MissingField(session_token), got {:?}", other), + } + } +} diff --git a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_error.rs b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_error.rs index 1f42e2e2e5..b7b6aae059 100644 --- a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_error.rs +++ b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_error.rs @@ -8,6 +8,9 @@ pub enum HttpUserSessionPayloadError { /// Error reading HTTP header HttpSessionHeaderError(String), + /// Session payload is missing a required field. + MissingField(&'static str), + /// Error encoding, decoding, or constructing the JWT signer. JwtSigner(JwtSignerError), } @@ -29,6 +32,10 @@ impl HttpUserSessionPayloadError { ) => { true }, + // Missing field in a verified JWT — how did this make it into the wild!? → 500 + HttpUserSessionPayloadError::MissingField(_) => { + true + } } } } @@ -37,6 +44,7 @@ impl Display for HttpUserSessionPayloadError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::HttpSessionHeaderError(msg) => write!(f, "HTTP session header error: {}", msg), + Self::MissingField(field) => write!(f, "HTTP session payload missing field: {}", field), Self::JwtSigner(e) => write!(f, "JWT signer error: {}", e), } } diff --git a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_signer.rs b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_signer.rs index dbae7a914b..d66d9237d9 100644 --- a/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_signer.rs +++ b/crates/lib/actix_artcraft/src/sessions/user_sessions/http_user_session_payload_signer.rs @@ -48,15 +48,7 @@ impl HttpUserSessionPayloadSigner { session_payload_contents: &str, ) -> Result { let claims = self.jwt_signer.jwt_to_claims(session_payload_contents)?; - - let session_token = claims["session_token"].clone(); - let maybe_user_token = claims.get("user_token") - .map(|t| t.to_string()); - - Ok(HttpUserSessionPayload { - session_token, - maybe_user_token, - }) + HttpUserSessionPayload::from_map(claims) } } diff --git a/crates/service/web/storyteller_web/src/api_doc_cli.rs b/crates/service/web/storyteller_web/src/api_doc_cli.rs index 5aba19e7b0..3f61674407 100644 --- a/crates/service/web/storyteller_web/src/api_doc_cli.rs +++ b/crates/service/web/storyteller_web/src/api_doc_cli.rs @@ -30,7 +30,6 @@ pub mod billing; pub mod configs; pub mod docs; pub mod email; -pub mod error; pub mod http_server; pub mod startup; pub mod state; diff --git a/crates/service/web/storyteller_web/src/api_doc_server.rs b/crates/service/web/storyteller_web/src/api_doc_server.rs index 4463f0efc6..87ce475942 100644 --- a/crates/service/web/storyteller_web/src/api_doc_server.rs +++ b/crates/service/web/storyteller_web/src/api_doc_server.rs @@ -32,7 +32,6 @@ pub mod billing; pub mod configs; pub mod docs; pub mod email; -pub mod error; pub mod http_server; pub mod startup; pub mod state; diff --git a/crates/service/web/storyteller_web/src/error/internal_error.rs b/crates/service/web/storyteller_web/src/error/internal_error.rs deleted file mode 100644 index 0c479310bd..0000000000 --- a/crates/service/web/storyteller_web/src/error/internal_error.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; - -/// Any generic errors arising internally. -/// An error class that we do not expose via the API. -/// This should be massaged before we send it to the user. -#[derive(Debug)] -pub enum InternalError { - /// Issue with HMAC decoding - /// This is typically when using development cookies against production, - /// or vice versa. - VisitorCookieError(String), - - /// Decode error with AVT cookie missing a required field. - VisitorCookieMissingField(&'static str), - - /// Catch instances of anyhow::Error. - AnyhowError(anyhow::Error), -} - -impl Error for InternalError {} - -impl Display for InternalError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - InternalError::VisitorCookieError(reason) => write!(f, "Visitor cookie error: {:?}", reason), - InternalError::VisitorCookieMissingField(field) => write!(f, "Visitor cookie missing field: {}", field), - InternalError::AnyhowError(err) => write!(f, "Anyhow error: {}", err), - } - } -} - -impl From for InternalError { - fn from(err: anyhow::Error) -> Self { - InternalError::AnyhowError(err) - } -} diff --git a/crates/service/web/storyteller_web/src/error/mod.rs b/crates/service/web/storyteller_web/src/error/mod.rs deleted file mode 100644 index 3e6d672990..0000000000 --- a/crates/service/web/storyteller_web/src/error/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod internal_error; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/main.rs b/crates/service/web/storyteller_web/src/main.rs index 384f73529b..34a56b5a9c 100644 --- a/crates/service/web/storyteller_web/src/main.rs +++ b/crates/service/web/storyteller_web/src/main.rs @@ -93,7 +93,6 @@ use crate::util::troll_user_bans::troll_user_ban_list::TrollUserBanList; pub mod billing; pub mod configs; pub mod email; -pub mod error; pub mod http_server; pub mod startup; pub mod state;