Compare commits

...
8 changed files with 73 additions and 50 deletions
@@ -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<String>,
}
impl HttpUserSessionPayload {
pub fn from_map(
map: BTreeMap<String, String>,
) -> Result<Self, HttpUserSessionPayloadError> {
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<String, String> {
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),
}
}
}
@@ -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),
}
}
@@ -48,15 +48,7 @@ impl HttpUserSessionPayloadSigner {
session_payload_contents: &str,
) -> Result<HttpUserSessionPayload, HttpUserSessionPayloadError> {
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)
}
}
@@ -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;
@@ -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;
@@ -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<anyhow::Error> for InternalError {
fn from(err: anyhow::Error) -> Self {
InternalError::AnyhowError(err)
}
}
@@ -1 +0,0 @@
pub mod internal_error;
@@ -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;