mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
avt cleanup (fix paging) (#1312)
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
use actix_web::cookie::time::OffsetDateTime;
|
||||
use actix_web::cookie::{Cookie, SameSite};
|
||||
use actix_web::HttpRequest;
|
||||
use log::warn;
|
||||
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_payload_signer::AvtPayloadSigner;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
const VISITOR_COOKIE_NAME : &str = "visitor";
|
||||
|
||||
/// Handle "anonymous visitor tracking" cookies.
|
||||
/// This enables us to associate results with an anonymous user for a better experience,
|
||||
/// as well as do some form of return visitor tracking.
|
||||
#[derive(Clone)]
|
||||
pub struct AvtCookieManager {
|
||||
cookie_domain: String,
|
||||
payload_signer: AvtPayloadSigner,
|
||||
}
|
||||
|
||||
impl AvtCookieManager {
|
||||
|
||||
pub fn new(cookie_domain: &str, hmac_secret: &str) -> Result<Self, AvtCookiePayloadError> {
|
||||
Ok(Self {
|
||||
cookie_domain: cookie_domain.to_string(),
|
||||
payload_signer: AvtPayloadSigner::new(hmac_secret)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_new_cookie(&self) -> Result<Cookie, AvtCookiePayloadError> {
|
||||
let token = AnonymousVisitorTrackingToken::generate();
|
||||
self.make_new_cookie_with_apriori_token(&token)
|
||||
}
|
||||
|
||||
pub fn make_new_cookie_with_apriori_token(
|
||||
&self,
|
||||
token: &AnonymousVisitorTrackingToken,
|
||||
) -> Result<Cookie, AvtCookiePayloadError> {
|
||||
let payload = AvtCookiePayload::from_token(token.clone());
|
||||
let jwt_string = self.payload_signer.encode(&payload)?;
|
||||
|
||||
let make_secure = !self.cookie_domain.to_lowercase().contains("jungle.horse")
|
||||
&& !self.cookie_domain.to_lowercase().contains("localhost");
|
||||
|
||||
let same_site = if make_secure {
|
||||
SameSite::None // NB: Allow usage from other domains
|
||||
} else {
|
||||
SameSite::Lax // NB: You can't set "SameSite=None" on non-secure cookies
|
||||
};
|
||||
|
||||
Ok(Cookie::build(VISITOR_COOKIE_NAME, jwt_string)
|
||||
.secure(make_secure) // HTTPS-only
|
||||
.same_site(same_site)
|
||||
.permanent()
|
||||
.path("/") // NB: Otherwise it'll be set to `/v1`
|
||||
//.domain(&self.cookie_domain)
|
||||
//.http_only(true) // Not exposed to Javascript
|
||||
.finish())
|
||||
}
|
||||
|
||||
pub fn make_delete_cookie(&self) -> Cookie {
|
||||
let mut cookie = Cookie::build(VISITOR_COOKIE_NAME, "DELETED")
|
||||
.expires(OffsetDateTime::UNIX_EPOCH)
|
||||
.path("/") // NB: Otherwise it'll be set to `/v1`
|
||||
.finish();
|
||||
cookie.make_removal();
|
||||
cookie
|
||||
}
|
||||
|
||||
pub fn decode_cookie_payload(
|
||||
&self,
|
||||
visitor_cookie: &Cookie,
|
||||
) -> Result<AvtCookiePayload, AvtCookiePayloadError> {
|
||||
self.payload_signer.decode(visitor_cookie.value())
|
||||
}
|
||||
|
||||
pub fn decode_cookie_payload_from_request(
|
||||
&self,
|
||||
request: &HttpRequest,
|
||||
) -> Result<Option<AvtCookiePayload>, AvtCookiePayloadError> {
|
||||
let cookie = match request.cookie(VISITOR_COOKIE_NAME) {
|
||||
None => return Ok(None),
|
||||
Some(cookie) => cookie,
|
||||
};
|
||||
|
||||
match self.decode_cookie_payload(&cookie) {
|
||||
Err(e) => {
|
||||
warn!("Visitor cookie decode error: {:?}", e);
|
||||
Err(e)
|
||||
},
|
||||
Ok(payload) => Ok(Some(payload)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_avt_token_from_request(
|
||||
&self,
|
||||
request: &HttpRequest,
|
||||
) -> Option<AnonymousVisitorTrackingToken> {
|
||||
self.decode_cookie_payload_from_request(request)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|payload| payload.avt_token)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::test::TestRequest;
|
||||
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
#[test]
|
||||
fn test_create_cookie() {
|
||||
// NB: Stable encoding test. If this changes we should bump the cookie version
|
||||
// so we don't accidentally invalidate visitor tracking on existing cookies.
|
||||
let manager = AvtCookieManager::new("fakeyou.com", "secret").unwrap();
|
||||
let token = AnonymousVisitorTrackingToken::new_from_str("avt_ex_anonymous_visitor_tracking_token");
|
||||
let cookie = manager.make_new_cookie_with_apriori_token(&token).unwrap();
|
||||
|
||||
assert_eq!(cookie.value(), "eyJhbGciOiJIUzI1NiJ9.eyJhdnRfdG9rZW4iOiJhdnRfZXhfYW5vbnltb3VzX3Zpc2l0b3JfdHJhY2tpbmdfdG9rZW4iLCJjb29raWVfdmVyc2lvbiI6IjEifQ.w0KRxBxiyBfTZGxaezRa0IgD8ojmCnhRH4SS33Nq3O8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cookie_round_trip() {
|
||||
let manager = AvtCookieManager::new("fakeyou.com", "secret").unwrap();
|
||||
let token = AnonymousVisitorTrackingToken::new_from_str("avt_ex_anonymous_visitor_tracking_token");
|
||||
let cookie = manager.make_new_cookie_with_apriori_token(&token).unwrap();
|
||||
|
||||
let http_request = TestRequest::default()
|
||||
.cookie(cookie)
|
||||
.to_http_request();
|
||||
|
||||
let decoded = manager.decode_cookie_payload_from_request(&http_request)
|
||||
.expect("no error")
|
||||
.expect("must exist");
|
||||
|
||||
assert_eq!(decoded.avt_token.as_str(), "avt_ex_anonymous_visitor_tracking_token");
|
||||
assert_eq!(decoded.cookie_version, 1);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::error::internal_error::InternalError;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
/**
|
||||
@@ -43,18 +43,18 @@ impl AvtCookiePayload {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_map(map: BTreeMap<String, String>) -> Result<Self, InternalError> {
|
||||
pub fn from_map(map: BTreeMap<String, String>) -> Result<Self, AvtCookiePayloadError> {
|
||||
let avt_token = map.get("avt_token")
|
||||
.ok_or_else(|| InternalError::VisitorCookieMissingField("avt_token"))?;
|
||||
.ok_or(AvtCookiePayloadError::MissingField("avt_token"))?;
|
||||
|
||||
let cookie_version = map
|
||||
.get("cookie_version")
|
||||
.ok_or_else(|| InternalError::VisitorCookieMissingField("cookie_version"))?;
|
||||
.ok_or(AvtCookiePayloadError::MissingField("cookie_version"))?;
|
||||
|
||||
let avt_token = AnonymousVisitorTrackingToken::new_from_str(avt_token);
|
||||
|
||||
let cookie_version = u32::from_str(cookie_version)
|
||||
.map_err(|e| InternalError::VisitorCookieError(
|
||||
.map_err(|e| AvtCookiePayloadError::PayloadDecodeError(
|
||||
format!("invalid integer for cookie_version: {:?}, version: {}", e, cookie_version)))?;
|
||||
|
||||
Ok(AvtCookiePayload {
|
||||
@@ -75,7 +75,7 @@ impl AvtCookiePayload {
|
||||
mod tests {
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use jwt_signer::jwt_signer_error::JwtSignerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AvtCookiePayloadError {
|
||||
/// AVT cookie payload is missing a required field.
|
||||
MissingField(&'static str),
|
||||
|
||||
/// Generic decode error (e.g. invalid integer for cookie_version).
|
||||
PayloadDecodeError(String),
|
||||
|
||||
/// Error encoding, decoding, or constructing the JWT signer.
|
||||
JwtSigner(JwtSignerError),
|
||||
}
|
||||
|
||||
impl AvtCookiePayloadError {
|
||||
pub fn is_server_error(&self) -> bool {
|
||||
match self {
|
||||
// JWT verify errors (eg. forged cookies) → 400 bad input.
|
||||
AvtCookiePayloadError::JwtSigner(JwtSignerError::JwtVerifyError(_)) => {
|
||||
false
|
||||
},
|
||||
// Server-side JWT signer failures (bad HMAC config, signing failure) → 500.
|
||||
AvtCookiePayloadError::JwtSigner(
|
||||
JwtSignerError::JwtInvalidKeyLength | JwtSignerError::JwtSignError(_)
|
||||
) => {
|
||||
true
|
||||
},
|
||||
// Other payload decode failures (how did these make it into the wild!?) → 500
|
||||
AvtCookiePayloadError::MissingField(_) | AvtCookiePayloadError::PayloadDecodeError(_) => {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AvtCookiePayloadError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingField(field) => write!(f, "AVT cookie missing field: {}", field),
|
||||
Self::PayloadDecodeError(reason) => write!(f, "AVT cookie decode error: {}", reason),
|
||||
Self::JwtSigner(e) => write!(f, "JWT signer error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for AvtCookiePayloadError {}
|
||||
|
||||
impl From<JwtSignerError> for AvtCookiePayloadError {
|
||||
fn from(err: JwtSignerError) -> Self {
|
||||
Self::JwtSigner(err)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError;
|
||||
use jwt_signer::jwt_signer::JwtSigner;
|
||||
|
||||
/// Signs and verifies the AVT cookie payload using HMAC-signed JWTs.
|
||||
/// This module is intentionally HTTP-agnostic. The HTTP / cookie wrapper lives in `avt_cookie_manager`.
|
||||
#[derive(Clone)]
|
||||
pub struct AvtPayloadSigner {
|
||||
jwt_signer: JwtSigner,
|
||||
}
|
||||
|
||||
impl AvtPayloadSigner {
|
||||
pub fn new(hmac_secret: &str) -> Result<Self, AvtCookiePayloadError> {
|
||||
Ok(Self {
|
||||
jwt_signer: JwtSigner::new(hmac_secret)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(&self, payload: &AvtCookiePayload) -> Result<String, AvtCookiePayloadError> {
|
||||
let claims = payload.to_map();
|
||||
let jwt_string = self.jwt_signer.claims_to_jwt(&claims)?;
|
||||
Ok(jwt_string)
|
||||
}
|
||||
|
||||
pub fn decode(&self, jwt: &str) -> Result<AvtCookiePayload, AvtCookiePayloadError> {
|
||||
let claims = self.jwt_signer.jwt_to_claims(jwt)?;
|
||||
AvtCookiePayload::from_map(claims)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
use crate::sessions::anonymous_visitor_tracking::avt_payload_signer::AvtPayloadSigner;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
#[test]
|
||||
fn test_encode() {
|
||||
// NB: Stable encoding test. If this changes we should bump the cookie version.
|
||||
let signer = AvtPayloadSigner::new("fake_secret").unwrap();
|
||||
let token = AnonymousVisitorTrackingToken::new_from_str("avt_ex_anonymous_visitor_tracking_token");
|
||||
let payload = AvtCookiePayload::from_token(token);
|
||||
|
||||
let encoded = signer.encode(&payload).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
encoded,
|
||||
"eyJhbGciOiJIUzI1NiJ9.eyJhdnRfdG9rZW4iOiJhdnRfZXhfYW5vbnltb3VzX3Zpc2l0b3JfdHJhY2tpbmdfdG9rZW4iLCJjb29raWVfdmVyc2lvbiI6IjEifQ.5jgQfSXue684okKBzW3847jEhL7eLQKzelyAkgnwe_I"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_version_1() {
|
||||
// NB: Version 1 payload. The expected encoded value below was generated by `test_encode`.
|
||||
let payload =
|
||||
"eyJhbGciOiJIUzI1NiJ9.eyJhdnRfdG9rZW4iOiJhdnRfZXhfYW5vbnltb3VzX3Zpc2l0b3JfdHJhY2tpbmdfdG9rZW4iLCJjb29raWVfdmVyc2lvbiI6IjEifQ.5jgQfSXue684okKBzW3847jEhL7eLQKzelyAkgnwe_I";
|
||||
|
||||
let signer = AvtPayloadSigner::new("fake_secret").unwrap();
|
||||
let decoded = signer.decode(payload).unwrap();
|
||||
|
||||
assert_eq!(decoded.avt_token.as_str(), "avt_ex_anonymous_visitor_tracking_token");
|
||||
assert_eq!(decoded.cookie_version, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_round_trip() {
|
||||
let signer = AvtPayloadSigner::new("fake_secret").unwrap();
|
||||
let token = AnonymousVisitorTrackingToken::new_from_str("avt_ex_anonymous_visitor_tracking_token");
|
||||
let payload = AvtCookiePayload::from_token(token);
|
||||
|
||||
let encoded = signer.encode(&payload).unwrap();
|
||||
let decoded = signer.decode(&encoded).unwrap();
|
||||
|
||||
assert_eq!(decoded.avt_token.as_str(), "avt_ex_anonymous_visitor_tracking_token");
|
||||
assert_eq!(decoded.cookie_version, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod avt_cookie_manager;
|
||||
pub mod avt_cookie_payload;
|
||||
pub mod avt_cookie_payload_error;
|
||||
pub mod avt_payload_signer;
|
||||
@@ -1,4 +1,2 @@
|
||||
pub mod http_user_session_manager;
|
||||
pub mod http_user_session_payload;
|
||||
pub mod http_user_session_payload_error;
|
||||
pub mod http_user_session_payload_signer;
|
||||
pub mod anonymous_visitor_tracking;
|
||||
pub mod user_sessions;
|
||||
|
||||
+4
-4
@@ -8,9 +8,9 @@ use actix_web::cookie::{Cookie, SameSite};
|
||||
use actix_web::HttpRequest;
|
||||
use log::warn;
|
||||
|
||||
use crate::sessions::http_user_session_payload::HttpUserSessionPayload;
|
||||
use crate::sessions::http_user_session_payload_signer::HttpUserSessionPayloadSigner;
|
||||
use crate::sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use crate::sessions::user_sessions::http_user_session_payload::HttpUserSessionPayload;
|
||||
use crate::sessions::user_sessions::http_user_session_payload_signer::HttpUserSessionPayloadSigner;
|
||||
use crate::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use tokens::tokens::user_sessions::UserSessionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
use tokens::tokens::user_sessions::UserSessionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use crate::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
|
||||
#[test]
|
||||
fn test_create_cookie_payload() {
|
||||
+21
@@ -12,6 +12,27 @@ pub enum HttpUserSessionPayloadError {
|
||||
JwtSigner(JwtSignerError),
|
||||
}
|
||||
|
||||
impl HttpUserSessionPayloadError {
|
||||
pub fn is_server_error(&self) -> bool {
|
||||
match self {
|
||||
// Client HTTP header errors → 400 bad input.
|
||||
HttpUserSessionPayloadError::HttpSessionHeaderError(_) => {
|
||||
false
|
||||
}
|
||||
// JWT verify errors (eg. forged cookies) → 400 bad input.
|
||||
HttpUserSessionPayloadError::JwtSigner(JwtSignerError::JwtVerifyError(_)) => {
|
||||
false
|
||||
}
|
||||
// Server-side JWT signer failures (bad HMAC config, signing failure) → 500.
|
||||
HttpUserSessionPayloadError::JwtSigner(
|
||||
JwtSignerError::JwtInvalidKeyLength | JwtSignerError::JwtSignError(_)
|
||||
) => {
|
||||
true
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HttpUserSessionPayloadError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::sessions::http_user_session_payload::HttpUserSessionPayload;
|
||||
use crate::sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use crate::sessions::user_sessions::http_user_session_payload::HttpUserSessionPayload;
|
||||
use crate::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use jwt_signer::jwt_signer::JwtSigner;
|
||||
use tokens::tokens::user_sessions::UserSessionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
@@ -62,7 +62,7 @@ impl HttpUserSessionPayloadSigner {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::sessions::http_user_session_payload_signer::HttpUserSessionPayloadSigner;
|
||||
use crate::sessions::user_sessions::http_user_session_payload_signer::HttpUserSessionPayloadSigner;
|
||||
use tokens::tokens::user_sessions::UserSessionToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod http_user_session_manager;
|
||||
pub mod http_user_session_payload;
|
||||
pub mod http_user_session_payload_error;
|
||||
pub mod http_user_session_payload_signer;
|
||||
+1
-1
@@ -5,7 +5,7 @@ use crate::endpoints::checkout_with_user_signup::user_creation_case::user_creati
|
||||
use crate::endpoints::checkout_with_user_signup::user_exists_case::user_exists_case;
|
||||
use crate::utils::artcraft_stripe_config::ArtcraftStripeConfigWithClient;
|
||||
use crate::utils::common_web_error::CommonWebError;
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::web::{Data, Json};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use artcraft_api_defs::stripe_artcraft::create_create_new_user_account_and_subscription_checkout::{PlanBillingCadence, SessionDetails, StripeArtcraftCreateSubscriptionCheckoutWithUserSignupRequest, StripeArtcraftCreateSubscriptionCheckoutWithUserSignupResponse, UserDetails};
|
||||
|
||||
+18
-4
@@ -4,7 +4,8 @@ use std::sync::Arc;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::session::session_checker_error::SessionCheckerError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use actix_artcraft::sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use actix_http::StatusCode;
|
||||
use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError};
|
||||
use anyhow::anyhow;
|
||||
@@ -198,9 +199,22 @@ impl From<RequireUserSessionError> for AdvancedCommonWebError {
|
||||
}
|
||||
|
||||
impl From<HttpUserSessionPayloadError> for AdvancedCommonWebError {
|
||||
fn from(_value: HttpUserSessionPayloadError) -> Self {
|
||||
// Any session error (bad header, bad JWT, construction failure) becomes 401.
|
||||
Self::NotAuthorized
|
||||
fn from(err: HttpUserSessionPayloadError) -> Self {
|
||||
if err.is_server_error() {
|
||||
Self::from_error(err)
|
||||
} else {
|
||||
Self::BadInputWithSimpleMessage("invalid session".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AvtCookiePayloadError> for AdvancedCommonWebError {
|
||||
fn from(err: AvtCookiePayloadError) -> Self {
|
||||
if err.is_server_error() {
|
||||
Self::from_error(err)
|
||||
} else {
|
||||
Self::BadInputWithSimpleMessage("invalid AVT cookie".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
use actix_web::cookie::time::OffsetDateTime;
|
||||
use actix_web::cookie::{Cookie, SameSite};
|
||||
use actix_web::HttpRequest;
|
||||
use anyhow::anyhow;
|
||||
use log::warn;
|
||||
|
||||
use crate::error::internal_error::InternalError;
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_payload::AvtCookiePayload;
|
||||
use jwt_signer::jwt_signer::JwtSigner;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
|
||||
const VISITOR_COOKIE_NAME : &str = "visitor";
|
||||
|
||||
/// Handle "anonymous visitor tracking" cookies.
|
||||
/// This enables us to associate results with an anonymous user for a better experience,
|
||||
/// as well as do some form of return visitor tracking.
|
||||
#[derive(Clone)]
|
||||
pub struct AvtCookieManager {
|
||||
cookie_domain: String,
|
||||
jwt_signer: JwtSigner,
|
||||
}
|
||||
|
||||
impl AvtCookieManager {
|
||||
|
||||
pub fn new(cookie_domain: &str, hmac_secret: &str) -> AnyhowResult<Self> {
|
||||
Ok(Self {
|
||||
cookie_domain: cookie_domain.to_string(),
|
||||
jwt_signer: JwtSigner::new(hmac_secret)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_new_cookie(&self) -> AnyhowResult<Cookie> {
|
||||
let token = AnonymousVisitorTrackingToken::generate();
|
||||
self.make_new_cookie_with_apriori_token(&token)
|
||||
}
|
||||
|
||||
pub fn make_new_cookie_with_apriori_token(&self, token: &AnonymousVisitorTrackingToken) -> AnyhowResult<Cookie> {
|
||||
let payload = AvtCookiePayload::from_token(token.clone());
|
||||
let claims = payload.to_map();
|
||||
let jwt_string = self.jwt_signer.claims_to_jwt(&claims)?;
|
||||
|
||||
let make_secure = !self.cookie_domain.to_lowercase().contains("jungle.horse")
|
||||
&& !self.cookie_domain.to_lowercase().contains("localhost");
|
||||
|
||||
let same_site = if make_secure {
|
||||
SameSite::None // NB: Allow usage from other domains
|
||||
} else {
|
||||
SameSite::Lax // NB: You can't set "SameSite=None" on non-secure cookies
|
||||
};
|
||||
|
||||
Ok(Cookie::build(VISITOR_COOKIE_NAME, jwt_string)
|
||||
.secure(make_secure) // HTTPS-only
|
||||
.same_site(same_site)
|
||||
.permanent()
|
||||
.path("/") // NB: Otherwise it'll be set to `/v1`
|
||||
//.domain(&self.cookie_domain)
|
||||
//.http_only(true) // Not exposed to Javascript
|
||||
.finish())
|
||||
}
|
||||
|
||||
pub fn make_delete_cookie(&self) -> Cookie {
|
||||
let mut cookie = Cookie::build(VISITOR_COOKIE_NAME, "DELETED")
|
||||
.expires(OffsetDateTime::UNIX_EPOCH)
|
||||
.path("/") // NB: Otherwise it'll be set to `/v1`
|
||||
.finish();
|
||||
cookie.make_removal();
|
||||
cookie
|
||||
}
|
||||
|
||||
pub fn decode_cookie_payload(&self, visitor_cookie: &Cookie) -> AnyhowResult<AvtCookiePayload> {
|
||||
let cookie_contents = visitor_cookie.value().to_string();
|
||||
let claims = self.jwt_signer.jwt_to_claims(&cookie_contents)?;
|
||||
let payload = AvtCookiePayload::from_map(claims)?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
pub fn decode_cookie_payload_from_request(&self, request: &HttpRequest) -> Result<Option<AvtCookiePayload>, InternalError> {
|
||||
let cookie = match request.cookie(VISITOR_COOKIE_NAME) {
|
||||
None => return Ok(None),
|
||||
Some(cookie) => cookie,
|
||||
};
|
||||
|
||||
match self.decode_cookie_payload(&cookie) {
|
||||
Err(e) => {
|
||||
warn!("Visitor cookie decode error: {:?}", e);
|
||||
Err(InternalError::VisitorCookieError(e.to_string()))
|
||||
},
|
||||
Ok(payload) => Ok(Some(payload)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_avt_token_from_request(&self, request: &HttpRequest) -> Option<AnonymousVisitorTrackingToken> {
|
||||
self.decode_cookie_payload_from_request(request)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|payload| payload.avt_token)
|
||||
}
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
pub mod avt_cookie_manager;
|
||||
pub mod avt_cookie_payload;
|
||||
@@ -1 +0,0 @@
|
||||
pub mod anonymous_visitor_tracking;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use crate::http_server::endpoints::app_state::components::get_permissions::{get_permissions, AppStatePermissions};
|
||||
use crate::http_server::endpoints::app_state::components::get_premium_info::{get_premium_info, AppStatePremiumInfo};
|
||||
use crate::http_server::endpoints::app_state::components::get_server_info::{get_server_info, AppStateServerInfo};
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ use crate::http_server::validations::validate_passwords::validate_passwords;
|
||||
use crate::http_server::validations::validate_username::validate_username;
|
||||
use crate::util::enroll_in_studio::enroll_in_studio;
|
||||
use actix_artcraft::requests::get_request_signup_source_enum::get_request_signup_source_enum;
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ use crate::http_server::endpoints::users::google_sso::check_claims::check_claims
|
||||
use crate::http_server::endpoints::users::google_sso::handle_existing_sso_account::{handle_existing_sso_account, ExistingAccountArgs};
|
||||
use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::{handle_new_sso_account, NewSsoArgs};
|
||||
use crate::state::certs::google_sign_in_cert::GoogleSignInCert;
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Data, Json};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse, ResponseError};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::http_server::session::session_checker::SessionChecker;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod common_requests;
|
||||
pub mod common_responses;
|
||||
pub mod endpoint_helpers;
|
||||
pub mod cookies;
|
||||
pub mod deprecated_endpoints;
|
||||
pub mod endpoints;
|
||||
pub mod middleware;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_web::HttpRequest;
|
||||
use log::warn;
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::{get_user_session_by_token, get_user_session_by_token_pooled_connection, SessionUserRecord};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use actix_artcraft::sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SessionCheckerError {
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::Actor;
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_cors_configs::cors::build_cors_config;
|
||||
use actix_cors_configs::shared_array_buffer_cors::shared_array_buffer_cors;
|
||||
use actix_helpers::middleware::banned_cidr_filter::banned_cidr_filter::BannedCidrFilter;
|
||||
@@ -71,7 +71,7 @@ use crate::billing::stripe_internal_user_lookup_impl::StripeInternalUserLookupIm
|
||||
use crate::configs::app_startup::redis_rate_limiters::configure_redis_rate_limiters;
|
||||
use crate::configs::connect_to_database::connect_to_database;
|
||||
use crate::configs::static_api_tokens::StaticApiTokenSet;
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use crate::http_server::middleware::error_alerting_middleware::error_alerting_middleware::ErrorAlertingMiddleware;
|
||||
use crate::http_server::middleware::pushback_filter_middleware::PushbackFilter;
|
||||
use crate::http_server::routes::add_routes::add_routes;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use actix_artcraft::sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_manager::HttpUserSessionManager;
|
||||
use actix_helpers::middleware::banned_cidr_filter::banned_cidr_set::BannedCidrSet;
|
||||
use actix_helpers::middleware::banned_ip_filter::ip_ban_list::ip_ban_list::IpBanList;
|
||||
use billing_artcraft_component::utils::artcraft_stripe_config::ArtcraftStripeConfigWithClient;
|
||||
@@ -25,7 +25,7 @@ use url_config::third_party_url_redirector::ThirdPartyUrlRedirector;
|
||||
|
||||
use crate::configs::app_startup::username_set::UsernameSet;
|
||||
use crate::configs::static_api_tokens::StaticApiTokenSet;
|
||||
use crate::http_server::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use crate::http_server::deprecated_endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::ModelTokensByCategoryToken;
|
||||
use crate::http_server::deprecated_endpoints::leaderboard::get_leaderboard::LeaderboardInfo;
|
||||
use crate::http_server::endpoints::media_files::list::list_featured_media_files_handler::ListFeaturedMediaFilesQueryParams;
|
||||
|
||||
Reference in New Issue
Block a user