1use std::{ops::Deref, sync::Arc, time::Duration};
2
3use chrono::{DateTime, FixedOffset, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6use thiserror::Error;
7
8use crate::time::now_offset;
9
10#[derive(Debug, Error)]
11pub enum CtxError {
12 #[error("Invalid payload, need a json object")]
13 InvalidPayload,
14
15 #[error("Unauthorized: {0}")]
16 Unauthorized(String),
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
21#[serde(transparent)]
22pub struct CtxPayload(Map<String, Value>);
23
24impl CtxPayload {
25 pub fn new(payload: Map<String, Value>) -> Self {
26 Self(payload)
27 }
28
29 pub fn into_inner(self) -> Map<String, Value> {
30 self.0
31 }
32
33 pub fn set_subject(&mut self, value: impl Into<String>) {
34 self.set_string(Ctx::SUB, value);
35 }
36
37 pub fn set_expires_at(&mut self, value: impl Into<DateTime<Utc>>) {
39 let utc: DateTime<Utc> = value.into();
40 self.set_exp(utc.timestamp());
41 }
42
43 pub fn set_exp(&mut self, epoch_seconds: i64) {
45 self.set_i64(Ctx::EXP, epoch_seconds);
46 }
47
48 pub fn set_string(&mut self, key: &str, value: impl Into<String>) {
49 self.0.insert(key.to_string(), Value::String(value.into()));
50 }
51
52 pub fn set_i64(&mut self, key: &str, value: i64) {
53 self.0.insert(key.to_string(), Value::Number(value.into()));
54 }
55
56 pub fn set_i32(&mut self, key: &str, value: i32) {
57 self.0.insert(key.to_string(), Value::Number(value.into()));
58 }
59
60 pub fn set_datetime(&mut self, key: &str, value: impl Into<DateTime<Utc>>) {
61 let utc: DateTime<Utc> = value.into();
62 self.0.insert(key.to_string(), Value::Number(utc.timestamp().into()));
63 }
64
65 pub fn set_bool(&mut self, key: &str, value: bool) {
66 self.0.insert(key.to_string(), Value::Bool(value));
67 }
68
69 pub fn set_strings<I, S>(&mut self, key: &str, value: I)
70 where
71 I: IntoIterator<Item = S>,
72 S: Into<String>,
73 {
74 self
75 .0
76 .insert(key.to_string(), Value::Array(value.into_iter().map(|s| Value::String(s.into())).collect()));
77 }
78
79 pub fn get_subject(&self) -> Option<&str> {
80 self.get_str(Ctx::SUB)
81 }
82
83 pub fn get_expires_at(&self) -> Option<DateTime<Utc>> {
84 self.get_exp().and_then(DateTime::<Utc>::from_timestamp_secs)
85 }
86
87 pub fn get_exp(&self) -> Option<i64> {
88 self.get_i64(Ctx::EXP)
89 }
90
91 pub fn get_str(&self, key: &str) -> Option<&str> {
92 self.0.get(key).and_then(|s| s.as_str())
93 }
94
95 pub fn get_i64(&self, key: &str) -> Option<i64> {
96 self.0.get(key).and_then(|s| s.as_i64())
97 }
98
99 pub fn get_i32(&self, key: &str) -> Option<i32> {
100 self.0.get(key).and_then(|s| s.as_i64()).map(|v| v as i32)
101 }
102
103 pub fn get_strings(&self, key: &str) -> Option<Vec<&str>> {
104 self
105 .0
106 .get(key)
107 .and_then(|s| s.as_array())
108 .map(|v| v.iter().filter_map(|s| s.as_str()).map(|s| s.trim()).collect())
109 }
110
111 pub fn get_bool(&self, key: &str) -> Option<bool> {
112 self.0.get(key).and_then(|s| s.as_bool())
113 }
114}
115
116impl From<Map<String, Value>> for CtxPayload {
117 fn from(payload: Map<String, Value>) -> Self {
118 Self::new(payload)
119 }
120}
121
122#[derive(Debug, Serialize, Deserialize)]
123#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
124pub struct CtxInner {
125 payload: CtxPayload,
126 req_time: DateTime<FixedOffset>,
127 req_id: String,
128}
129
130#[derive(Clone, Debug, Serialize, Deserialize)]
132#[cfg_attr(feature = "with-openapi", derive(utoipa::ToSchema))]
133#[serde(transparent)]
134pub struct Ctx(Arc<CtxInner>);
135
136impl Ctx {
137 pub const SUB: &str = "sub";
139 pub const EXP: &str = "exp";
141 pub const TENANT_ID: &str = "tenant_id";
143
144 pub(crate) fn new(payload: CtxPayload, req_time: DateTime<FixedOffset>, req_id: String) -> Self {
145 Self(Arc::new(CtxInner { payload, req_time, req_id }))
146 }
147
148 pub fn try_new(
150 mut payload: CtxPayload,
151 req_time: Option<DateTime<FixedOffset>>,
152 req_id: Option<String>,
153 ) -> Result<Self, CtxError> {
154 let now = now_offset();
155 if let Some(st) = payload.get_expires_at() {
156 if st < now {
157 return Err(CtxError::Unauthorized("The token expired".to_string()));
158 }
159 } else {
160 let exp = now + Duration::from_secs(60 * 60 * 24 * 60); payload.set_expires_at(exp.to_utc());
162 }
163
164 Ok(Self::new(payload, req_time.unwrap_or(now), req_id.unwrap_or_default()))
165 }
166
167 pub fn new_root() -> Self {
168 let req_time = now_offset();
169 let expires_at = req_time + Duration::from_secs(60 * 30); let mut payload = CtxPayload::default();
171 payload.set_string(Self::SUB, "0");
172 payload.set_expires_at(expires_at);
173 Self::new(payload, req_time, "".to_string())
174 }
175
176 pub fn new_super_admin() -> Self {
177 let req_time = now_offset();
178 let expires_at = req_time + Duration::from_secs(60 * 30); let mut payload = CtxPayload::default();
180 payload.set_string(Self::SUB, "1");
181 payload.set_expires_at(expires_at);
182 Self::new(payload, req_time, "".to_string())
183 }
184
185 pub fn get_user_id(&self) -> Option<i64> {
186 self.payload.get_i64(Self::SUB)
187 }
188
189 pub fn user_id(&self) -> i64 {
190 self.get_user_id().unwrap_or(0)
191 }
192
193 pub fn get_tenant_id(&self) -> Option<i64> {
194 self.payload.get_i64(Self::TENANT_ID)
195 }
196
197 pub fn tenant_id(&self) -> i64 {
198 self.get_tenant_id().unwrap_or(0)
199 }
200
201 pub fn req_time(&self) -> &DateTime<FixedOffset> {
202 &self.req_time
203 }
204
205 pub fn req_epoch_secs(&self) -> i64 {
206 self.req_time.timestamp()
207 }
208
209 pub fn req_epoch_millis(&self) -> i64 {
210 self.req_time.timestamp_millis()
211 }
212
213 pub fn req_id(&self) -> &str {
214 &self.req_id
215 }
216
217 pub fn expires_at(&self) -> Option<DateTime<Utc>> {
218 self.payload.get_expires_at()
219 }
220
221 pub fn payload(&self) -> &CtxPayload {
222 &self.payload
223 }
224}
225
226impl Deref for Ctx {
227 type Target = CtxInner;
228
229 fn deref(&self) -> &Self::Target {
230 &self.0
231 }
232}