use crate::error::{ApiError, ApiErrorKind, ApiResult};
use crate::server::AppState;
use actix_web::dev::Payload;
use actix_web::{web::Data, FromRequest, HttpRequest};
use autopush_common::notification::{STANDARD_NOTIFICATION_PREFIX, TOPIC_NOTIFICATION_PREFIX};
use fernet::MultiFernet;
use futures::future;
use uuid::Uuid;
#[derive(Debug)]
pub enum MessageId {
WithTopic {
uaid: Uuid,
channel_id: Uuid,
topic: String,
},
WithoutTopic {
uaid: Uuid,
channel_id: Uuid,
timestamp: u64,
},
}
impl FromRequest for MessageId {
type Error = ApiError;
type Future = future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let message_id_param = req
.match_info()
.get("message_id")
.expect("{message_id} must be part of the path");
let app_state: Data<AppState> = Data::extract(req)
.into_inner()
.expect("No server state found");
future::ready(MessageId::decrypt(&app_state.fernet, message_id_param))
}
}
impl MessageId {
pub fn encrypt(&self, fernet: &MultiFernet) -> String {
let id_str = match self {
MessageId::WithTopic {
uaid,
channel_id,
topic,
} => format!(
"{}:{}:{}:{}",
TOPIC_NOTIFICATION_PREFIX,
&uaid.as_simple(),
&channel_id.as_simple(),
topic
),
MessageId::WithoutTopic {
uaid,
channel_id,
timestamp,
} => format!(
"{}:{}:{}:{}",
STANDARD_NOTIFICATION_PREFIX,
uaid.as_simple(),
channel_id.as_simple(),
timestamp
),
};
fernet.encrypt(id_str.as_bytes())
}
pub fn decrypt(fernet: &MultiFernet, message_id: &str) -> ApiResult<Self> {
let decrypted_bytes = fernet
.decrypt(message_id)
.map_err(|_| ApiErrorKind::InvalidMessageId)?;
let decrypted_str = String::from_utf8_lossy(&decrypted_bytes);
let segments: Vec<_> = decrypted_str.split(':').collect();
if segments.len() != 4 {
return Err(ApiErrorKind::InvalidMessageId.into());
}
let (version, uaid, chid, topic_or_timestamp) =
(segments[0], segments[1], segments[2], segments[3]);
match version {
"01" => Ok(MessageId::WithTopic {
uaid: Uuid::parse_str(uaid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
channel_id: Uuid::parse_str(chid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
topic: topic_or_timestamp.to_string(),
}),
"02" => Ok(MessageId::WithoutTopic {
uaid: Uuid::parse_str(uaid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
channel_id: Uuid::parse_str(chid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
timestamp: topic_or_timestamp
.parse()
.map_err(|_| ApiErrorKind::InvalidMessageId)?,
}),
_ => Err(ApiErrorKind::InvalidMessageId.into()),
}
}
pub fn uaid(&self) -> Uuid {
match self {
MessageId::WithTopic { uaid, .. } => *uaid,
MessageId::WithoutTopic { uaid, .. } => *uaid,
}
}
pub fn sort_key(&self) -> String {
match self {
MessageId::WithTopic {
channel_id, topic, ..
} => format!(
"{}:{}:{}",
TOPIC_NOTIFICATION_PREFIX,
channel_id.as_hyphenated(),
topic
),
MessageId::WithoutTopic {
channel_id,
timestamp,
..
} => format!(
"{}:{}:{}",
STANDARD_NOTIFICATION_PREFIX,
timestamp,
channel_id.as_hyphenated()
),
}
}
}