1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum KeyClass {
7 Secret,
9 Publishable,
11}
12
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15pub struct Limits {
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub max_connections: Option<u32>,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub max_subscriptions: Option<u32>,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub max_snapshot_rows: Option<u32>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub max_messages_per_minute: Option<u32>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub max_bytes_per_minute: Option<u64>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub max_http_requests_per_minute: Option<u32>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub max_http_batch_addresses: Option<u32>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SessionClaims {
42 pub iss: String,
44 pub sub: String,
46 pub aud: String,
48 pub iat: u64,
50 pub nbf: u64,
52 pub exp: u64,
54 pub jti: String,
56 pub scope: String,
58 pub metering_key: String,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub deployment_id: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub origin: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none", rename = "client_ip")]
68 pub client_ip: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub limits: Option<Limits>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub plan: Option<String>,
75 #[serde(rename = "key_class")]
77 pub key_class: KeyClass,
78}
79
80impl SessionClaims {
81 pub fn builder(
83 iss: impl Into<String>,
84 sub: impl Into<String>,
85 aud: impl Into<String>,
86 ) -> SessionClaimsBuilder {
87 SessionClaimsBuilder::new(iss, sub, aud)
88 }
89
90 pub fn is_expired(&self, now: u64) -> bool {
92 self.exp <= now
93 }
94
95 pub fn is_valid(&self, now: u64) -> bool {
97 self.nbf <= now && self.iat <= now
98 }
99}
100
101pub struct SessionClaimsBuilder {
103 iss: String,
104 sub: String,
105 aud: String,
106 iat: u64,
107 nbf: u64,
108 exp: u64,
109 jti: String,
110 scope: String,
111 metering_key: String,
112 deployment_id: Option<String>,
113 origin: Option<String>,
114 client_ip: Option<String>,
115 limits: Option<Limits>,
116 plan: Option<String>,
117 key_class: KeyClass,
118}
119
120impl SessionClaimsBuilder {
121 fn new(iss: impl Into<String>, sub: impl Into<String>, aud: impl Into<String>) -> Self {
122 use std::time::{SystemTime, UNIX_EPOCH};
123 let now = SystemTime::now()
124 .duration_since(UNIX_EPOCH)
125 .expect("time should not be before epoch")
126 .as_secs();
127
128 Self {
129 iss: iss.into(),
130 sub: sub.into(),
131 aud: aud.into(),
132 iat: now,
133 nbf: now,
134 exp: now + crate::DEFAULT_SESSION_TTL_SECONDS,
135 jti: uuid::Uuid::new_v4().to_string(),
136 scope: "read".to_string(),
137 metering_key: String::new(),
138 deployment_id: None,
139 origin: None,
140 client_ip: None,
141 limits: None,
142 plan: None,
143 key_class: KeyClass::Publishable,
144 }
145 }
146
147 pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
148 self.exp = self.iat + ttl_seconds;
149 self
150 }
151
152 pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
153 self.scope = scope.into();
154 self
155 }
156
157 pub fn with_metering_key(mut self, key: impl Into<String>) -> Self {
158 self.metering_key = key.into();
159 self
160 }
161
162 pub fn with_deployment_id(mut self, id: impl Into<String>) -> Self {
163 self.deployment_id = Some(id.into());
164 self
165 }
166
167 pub fn with_origin(mut self, origin: impl Into<String>) -> Self {
168 self.origin = Some(origin.into());
169 self
170 }
171
172 pub fn with_client_ip(mut self, client_ip: impl Into<String>) -> Self {
173 self.client_ip = Some(client_ip.into());
174 self
175 }
176
177 pub fn with_limits(mut self, limits: Limits) -> Self {
178 self.limits = Some(limits);
179 self
180 }
181
182 pub fn with_plan(mut self, plan: impl Into<String>) -> Self {
183 self.plan = Some(plan.into());
184 self
185 }
186
187 pub fn with_key_class(mut self, key_class: KeyClass) -> Self {
188 self.key_class = key_class;
189 self
190 }
191
192 pub fn with_jti(mut self, jti: impl Into<String>) -> Self {
193 self.jti = jti.into();
194 self
195 }
196
197 pub fn build(self) -> SessionClaims {
198 SessionClaims {
199 iss: self.iss,
200 sub: self.sub,
201 aud: self.aud,
202 iat: self.iat,
203 nbf: self.nbf,
204 exp: self.exp,
205 jti: self.jti,
206 scope: self.scope,
207 metering_key: self.metering_key,
208 deployment_id: self.deployment_id,
209 origin: self.origin,
210 client_ip: self.client_ip,
211 limits: self.limits,
212 plan: self.plan,
213 key_class: self.key_class,
214 }
215 }
216}
217
218#[derive(Debug, Clone)]
220pub struct AuthContext {
221 pub subject: String,
223 pub issuer: String,
225 pub key_class: KeyClass,
227 pub metering_key: String,
229 pub deployment_id: Option<String>,
231 pub expires_at: u64,
233 pub scope: String,
235 pub limits: Limits,
237 pub plan: Option<String>,
239 pub origin: Option<String>,
241 pub client_ip: Option<String>,
243 pub jti: String,
245}
246
247impl AuthContext {
248 pub fn from_claims(claims: SessionClaims) -> Self {
250 Self {
251 subject: claims.sub,
252 issuer: claims.iss,
253 key_class: claims.key_class,
254 metering_key: claims.metering_key,
255 deployment_id: claims.deployment_id,
256 expires_at: claims.exp,
257 scope: claims.scope,
258 limits: claims.limits.unwrap_or_default(),
259 plan: claims.plan,
260 origin: claims.origin,
261 client_ip: claims.client_ip,
262 jti: claims.jti,
263 }
264 }
265}