autoendpoint/extractors/
message_id.rs1use crate::error::{ApiError, ApiErrorKind, ApiResult};
2use crate::server::AppState;
3use actix_web::dev::Payload;
4use actix_web::{web::Data, FromRequest, HttpRequest};
5use autopush_common::notification::{STANDARD_NOTIFICATION_PREFIX, TOPIC_NOTIFICATION_PREFIX};
6use fernet::MultiFernet;
7use futures::future;
8use uuid::Uuid;
9
10#[derive(Debug)]
15pub enum MessageId {
16 WithTopic {
17 uaid: Uuid,
18 channel_id: Uuid,
19 topic: String,
20 },
21 WithoutTopic {
22 uaid: Uuid,
23 channel_id: Uuid,
24 timestamp: u64,
25 },
26}
27
28impl FromRequest for MessageId {
29 type Error = ApiError;
30 type Future = future::Ready<Result<Self, Self::Error>>;
31
32 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
33 let message_id_param = req
34 .match_info()
35 .get("message_id")
36 .expect("{message_id} must be part of the path");
37 let app_state: Data<AppState> = Data::extract(req)
38 .into_inner()
39 .expect("No server state found");
40
41 future::ready(MessageId::decrypt(&app_state.fernet, message_id_param))
42 }
43}
44
45impl MessageId {
46 pub fn encrypt(&self, fernet: &MultiFernet) -> String {
48 let id_str = match self {
49 MessageId::WithTopic {
50 uaid,
51 channel_id,
52 topic,
53 } => format!(
54 "{}:{}:{}:{}",
55 TOPIC_NOTIFICATION_PREFIX,
56 &uaid.as_simple(),
57 &channel_id.as_simple(),
58 topic
59 ),
60 MessageId::WithoutTopic {
61 uaid,
62 channel_id,
63 timestamp,
64 } => format!(
65 "{}:{}:{}:{}",
66 STANDARD_NOTIFICATION_PREFIX,
67 uaid.as_simple(),
68 channel_id.as_simple(),
69 timestamp
70 ),
71 };
72
73 fernet.encrypt(id_str.as_bytes())
74 }
75
76 pub fn decrypt(fernet: &MultiFernet, message_id: &str) -> ApiResult<Self> {
78 let decrypted_bytes = fernet
79 .decrypt(message_id)
80 .map_err(|_| ApiErrorKind::InvalidMessageId)?;
81 let decrypted_str = String::from_utf8_lossy(&decrypted_bytes);
82 let segments: Vec<_> = decrypted_str.split(':').collect();
83
84 if segments.len() != 4 {
85 return Err(ApiErrorKind::InvalidMessageId.into());
86 }
87
88 let (version, uaid, chid, topic_or_timestamp) =
89 (segments[0], segments[1], segments[2], segments[3]);
90
91 match version {
92 "01" => Ok(MessageId::WithTopic {
93 uaid: Uuid::parse_str(uaid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
94 channel_id: Uuid::parse_str(chid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
95 topic: topic_or_timestamp.to_string(),
96 }),
97 "02" => Ok(MessageId::WithoutTopic {
98 uaid: Uuid::parse_str(uaid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
99 channel_id: Uuid::parse_str(chid).map_err(|_| ApiErrorKind::InvalidMessageId)?,
100 timestamp: topic_or_timestamp
101 .parse()
102 .map_err(|_| ApiErrorKind::InvalidMessageId)?,
103 }),
104 _ => Err(ApiErrorKind::InvalidMessageId.into()),
105 }
106 }
107
108 pub fn uaid(&self) -> Uuid {
110 match self {
111 MessageId::WithTopic { uaid, .. } => *uaid,
112 MessageId::WithoutTopic { uaid, .. } => *uaid,
113 }
114 }
115
116 pub fn sort_key(&self) -> String {
118 match self {
119 MessageId::WithTopic {
120 channel_id, topic, ..
121 } => format!(
122 "{}:{}:{}",
123 TOPIC_NOTIFICATION_PREFIX,
124 channel_id.as_hyphenated(),
125 topic
126 ),
127 MessageId::WithoutTopic {
128 channel_id,
129 timestamp,
130 ..
131 } => format!(
132 "{}:{}:{}",
133 STANDARD_NOTIFICATION_PREFIX,
134 timestamp,
135 channel_id.as_hyphenated()
136 ),
137 }
138 }
139}