Skip to main content

rustauth_plugins/api_key/
models.rs

1use rustauth_core::db::{DbRecord, DbValue};
2use rustauth_core::error::RustAuthError;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use time::OffsetDateTime;
6
7use super::options::ApiKeyPermissions;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct ApiKeyRecord {
12    pub id: String,
13    pub config_id: String,
14    pub name: Option<String>,
15    pub start: Option<String>,
16    pub prefix: Option<String>,
17    pub key: String,
18    pub reference_id: String,
19    pub refill_interval: Option<i64>,
20    pub refill_amount: Option<i64>,
21    pub last_refill_at: Option<OffsetDateTime>,
22    pub enabled: bool,
23    pub rate_limit_enabled: bool,
24    pub rate_limit_time_window: Option<i64>,
25    pub rate_limit_max: Option<i64>,
26    pub request_count: i64,
27    pub remaining: Option<i64>,
28    pub last_request: Option<OffsetDateTime>,
29    pub expires_at: Option<OffsetDateTime>,
30    pub created_at: OffsetDateTime,
31    pub updated_at: OffsetDateTime,
32    pub metadata: Option<Value>,
33    pub permissions: Option<ApiKeyPermissions>,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct ApiKeyPublicRecord {
39    pub id: String,
40    pub config_id: String,
41    pub name: Option<String>,
42    pub start: Option<String>,
43    pub prefix: Option<String>,
44    pub reference_id: String,
45    pub refill_interval: Option<i64>,
46    pub refill_amount: Option<i64>,
47    pub last_refill_at: Option<OffsetDateTime>,
48    pub enabled: bool,
49    pub rate_limit_enabled: bool,
50    pub rate_limit_time_window: Option<i64>,
51    pub rate_limit_max: Option<i64>,
52    pub request_count: i64,
53    pub remaining: Option<i64>,
54    pub last_request: Option<OffsetDateTime>,
55    pub expires_at: Option<OffsetDateTime>,
56    pub created_at: OffsetDateTime,
57    pub updated_at: OffsetDateTime,
58    pub metadata: Option<Value>,
59    pub permissions: Option<ApiKeyPermissions>,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct ApiKeyCreateRecord {
65    #[serde(flatten)]
66    pub record: ApiKeyPublicRecord,
67    pub key: String,
68}
69
70impl ApiKeyRecord {
71    pub fn public(&self) -> ApiKeyPublicRecord {
72        ApiKeyPublicRecord {
73            id: self.id.clone(),
74            config_id: self.config_id.clone(),
75            name: self.name.clone(),
76            start: self.start.clone(),
77            prefix: self.prefix.clone(),
78            reference_id: self.reference_id.clone(),
79            refill_interval: self.refill_interval,
80            refill_amount: self.refill_amount,
81            last_refill_at: self.last_refill_at,
82            enabled: self.enabled,
83            rate_limit_enabled: self.rate_limit_enabled,
84            rate_limit_time_window: self.rate_limit_time_window,
85            rate_limit_max: self.rate_limit_max,
86            request_count: self.request_count,
87            remaining: self.remaining,
88            last_request: self.last_request,
89            expires_at: self.expires_at,
90            created_at: self.created_at,
91            updated_at: self.updated_at,
92            metadata: normalize_metadata(self.metadata.clone()),
93            permissions: self.permissions.clone(),
94        }
95    }
96
97    pub(crate) fn normalized_metadata(&self) -> Option<Value> {
98        normalize_metadata(self.metadata.clone())
99    }
100
101    pub(crate) fn needs_metadata_migration(&self) -> bool {
102        matches!(self.metadata, Some(Value::String(_)))
103    }
104
105    pub fn to_record(&self) -> DbRecord {
106        DbRecord::from([
107            ("id".to_owned(), DbValue::String(self.id.clone())),
108            (
109                "config_id".to_owned(),
110                DbValue::String(self.config_id.clone()),
111            ),
112            ("name".to_owned(), optional_string(self.name.clone())),
113            ("start".to_owned(), optional_string(self.start.clone())),
114            ("prefix".to_owned(), optional_string(self.prefix.clone())),
115            ("key".to_owned(), DbValue::String(self.key.clone())),
116            (
117                "reference_id".to_owned(),
118                DbValue::String(self.reference_id.clone()),
119            ),
120            (
121                "refill_interval".to_owned(),
122                optional_number(self.refill_interval),
123            ),
124            (
125                "refill_amount".to_owned(),
126                optional_number(self.refill_amount),
127            ),
128            (
129                "last_refill_at".to_owned(),
130                optional_timestamp(self.last_refill_at),
131            ),
132            ("enabled".to_owned(), DbValue::Boolean(self.enabled)),
133            (
134                "rate_limit_enabled".to_owned(),
135                DbValue::Boolean(self.rate_limit_enabled),
136            ),
137            (
138                "rate_limit_time_window".to_owned(),
139                optional_number(self.rate_limit_time_window),
140            ),
141            (
142                "rate_limit_max".to_owned(),
143                optional_number(self.rate_limit_max),
144            ),
145            (
146                "request_count".to_owned(),
147                DbValue::Number(self.request_count),
148            ),
149            ("remaining".to_owned(), optional_number(self.remaining)),
150            (
151                "last_request".to_owned(),
152                optional_timestamp(self.last_request),
153            ),
154            ("expires_at".to_owned(), optional_timestamp(self.expires_at)),
155            ("created_at".to_owned(), DbValue::Timestamp(self.created_at)),
156            ("updated_at".to_owned(), DbValue::Timestamp(self.updated_at)),
157            (
158                "metadata".to_owned(),
159                self.metadata
160                    .clone()
161                    .map(DbValue::Json)
162                    .unwrap_or(DbValue::Null),
163            ),
164            (
165                "permissions".to_owned(),
166                self.permissions
167                    .as_ref()
168                    .and_then(|permissions| serde_json::to_value(permissions).ok())
169                    .map(DbValue::Json)
170                    .unwrap_or(DbValue::Null),
171            ),
172        ])
173    }
174}
175
176pub(crate) const API_KEY_FIELDS: [&str; 22] = [
177    "id",
178    "config_id",
179    "name",
180    "start",
181    "prefix",
182    "key",
183    "reference_id",
184    "refill_interval",
185    "refill_amount",
186    "last_refill_at",
187    "enabled",
188    "rate_limit_enabled",
189    "rate_limit_time_window",
190    "rate_limit_max",
191    "request_count",
192    "remaining",
193    "last_request",
194    "expires_at",
195    "created_at",
196    "updated_at",
197    "metadata",
198    "permissions",
199];
200
201pub(crate) fn record_from_db(record: DbRecord) -> Result<ApiKeyRecord, RustAuthError> {
202    Ok(ApiKeyRecord {
203        id: required_string(&record, "id")?.to_owned(),
204        config_id: required_string(&record, "config_id")?.to_owned(),
205        name: optional_string_field(&record, "name")?,
206        start: optional_string_field(&record, "start")?,
207        prefix: optional_string_field(&record, "prefix")?,
208        key: required_string(&record, "key")?.to_owned(),
209        reference_id: required_string(&record, "reference_id")?.to_owned(),
210        refill_interval: optional_number_field(&record, "refill_interval")?,
211        refill_amount: optional_number_field(&record, "refill_amount")?,
212        last_refill_at: optional_timestamp_field(&record, "last_refill_at")?,
213        enabled: optional_bool_field(&record, "enabled")?.unwrap_or(true),
214        rate_limit_enabled: optional_bool_field(&record, "rate_limit_enabled")?.unwrap_or(true),
215        rate_limit_time_window: optional_number_field(&record, "rate_limit_time_window")?,
216        rate_limit_max: optional_number_field(&record, "rate_limit_max")?,
217        request_count: optional_number_field(&record, "request_count")?.unwrap_or(0),
218        remaining: optional_number_field(&record, "remaining")?,
219        last_request: optional_timestamp_field(&record, "last_request")?,
220        expires_at: optional_timestamp_field(&record, "expires_at")?,
221        created_at: required_timestamp(&record, "created_at")?,
222        updated_at: required_timestamp(&record, "updated_at")?,
223        metadata: optional_json_field(&record, "metadata")?,
224        permissions: optional_json_field(&record, "permissions")?
225            .map(serde_json::from_value)
226            .transpose()
227            .map_err(|error| RustAuthError::Adapter(error.to_string()))?,
228    })
229}
230
231fn normalize_metadata(metadata: Option<Value>) -> Option<Value> {
232    match metadata {
233        Some(Value::String(value)) => serde_json::from_str(&value).ok(),
234        other => other,
235    }
236}
237
238fn optional_string(value: Option<String>) -> DbValue {
239    value.map(DbValue::String).unwrap_or(DbValue::Null)
240}
241
242fn optional_number(value: Option<i64>) -> DbValue {
243    value.map(DbValue::Number).unwrap_or(DbValue::Null)
244}
245
246fn optional_timestamp(value: Option<OffsetDateTime>) -> DbValue {
247    value.map(DbValue::Timestamp).unwrap_or(DbValue::Null)
248}
249
250fn required_string<'a>(record: &'a DbRecord, field: &str) -> Result<&'a str, RustAuthError> {
251    match record.get(field) {
252        Some(DbValue::String(value)) => Ok(value),
253        Some(DbValue::Null) | None => Err(RustAuthError::Adapter(format!(
254            "api key field `{field}` is missing"
255        ))),
256        Some(_) => Err(RustAuthError::Adapter(format!(
257            "api key field `{field}` has invalid type"
258        ))),
259    }
260}
261
262fn required_timestamp(record: &DbRecord, field: &str) -> Result<OffsetDateTime, RustAuthError> {
263    match record.get(field) {
264        Some(DbValue::Timestamp(value)) => Ok(*value),
265        Some(DbValue::Null) | None => Err(RustAuthError::Adapter(format!(
266            "api key field `{field}` is missing"
267        ))),
268        Some(_) => Err(RustAuthError::Adapter(format!(
269            "api key field `{field}` has invalid type"
270        ))),
271    }
272}
273
274fn optional_string_field(record: &DbRecord, field: &str) -> Result<Option<String>, RustAuthError> {
275    match record.get(field) {
276        Some(DbValue::String(value)) => Ok(Some(value.clone())),
277        Some(DbValue::Null) | None => Ok(None),
278        Some(_) => Err(RustAuthError::Adapter(format!(
279            "api key field `{field}` has invalid type"
280        ))),
281    }
282}
283
284fn optional_number_field(record: &DbRecord, field: &str) -> Result<Option<i64>, RustAuthError> {
285    match record.get(field) {
286        Some(DbValue::Number(value)) => Ok(Some(*value)),
287        Some(DbValue::Null) | None => Ok(None),
288        Some(_) => Err(RustAuthError::Adapter(format!(
289            "api key field `{field}` has invalid type"
290        ))),
291    }
292}
293
294fn optional_bool_field(record: &DbRecord, field: &str) -> Result<Option<bool>, RustAuthError> {
295    match record.get(field) {
296        Some(DbValue::Boolean(value)) => Ok(Some(*value)),
297        Some(DbValue::Null) | None => Ok(None),
298        Some(_) => Err(RustAuthError::Adapter(format!(
299            "api key field `{field}` has invalid type"
300        ))),
301    }
302}
303
304fn optional_timestamp_field(
305    record: &DbRecord,
306    field: &str,
307) -> Result<Option<OffsetDateTime>, RustAuthError> {
308    match record.get(field) {
309        Some(DbValue::Timestamp(value)) => Ok(Some(*value)),
310        Some(DbValue::Null) | None => Ok(None),
311        Some(_) => Err(RustAuthError::Adapter(format!(
312            "api key field `{field}` has invalid type"
313        ))),
314    }
315}
316
317fn optional_json_field(record: &DbRecord, field: &str) -> Result<Option<Value>, RustAuthError> {
318    match record.get(field) {
319        Some(DbValue::Json(value)) => Ok(Some(value.clone())),
320        Some(DbValue::String(value)) => serde_json::from_str(value)
321            .map(Some)
322            .map_err(|error| RustAuthError::Adapter(error.to_string())),
323        Some(DbValue::Null) | None => Ok(None),
324        Some(_) => Err(RustAuthError::Adapter(format!(
325            "api key field `{field}` has invalid type"
326        ))),
327    }
328}