From bacc6c98b26b4e15b69fe9f4b60c50faa460e294 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Wed, 13 May 2026 10:53:56 +0200 Subject: [PATCH] replace expiration field with custom type that merges to min value --- src/api/admin/admin_token.rs | 5 +++-- src/api/admin/key.rs | 7 ++++--- src/model/admin_token_table.rs | 5 +++-- src/model/key_table.rs | 5 +++-- src/model/permission.rs | 12 ++++++++++++ 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/api/admin/admin_token.rs b/src/api/admin/admin_token.rs index 1dd83c7c..b954b53e 100644 --- a/src/api/admin/admin_token.rs +++ b/src/api/admin/admin_token.rs @@ -7,6 +7,7 @@ use garage_util::time::now_msec; use garage_model::admin_token_table::*; use garage_model::garage::Garage; +use garage_model::permission::ExpirationTime; use crate::api::*; use crate::error::*; @@ -245,7 +246,7 @@ fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInf ), name: params.name.get().to_string(), expiration: params.expiration.get().inner().map(|x| { - DateTime::from_timestamp_millis(*x as i64).expect("invalid timestamp stored in db") + DateTime::from_timestamp_millis(x.0 as i64).expect("invalid timestamp stored in db") }), expired: params.is_expired(now), scope: params.scope.get().0.clone(), @@ -279,7 +280,7 @@ fn apply_token_updates( if let Some(expiration) = updates.expiration { params .expiration - .update(Some(expiration.timestamp_millis() as u64).into()); + .update(Some(ExpirationTime(expiration.timestamp_millis() as u64)).into()); } if updates.never_expires { params.expiration.update(None.into()); diff --git a/src/api/admin/key.rs b/src/api/admin/key.rs index e5024cf8..6a9cecd2 100644 --- a/src/api/admin/key.rs +++ b/src/api/admin/key.rs @@ -8,6 +8,7 @@ use garage_util::time::now_msec; use garage_model::garage::Garage; use garage_model::key_table::*; +use garage_model::permission::ExpirationTime; use crate::api::*; use crate::error::*; @@ -41,7 +42,7 @@ impl RequestHandler for ListKeysRequest { .expect("invalid timestamp stored in db") }), expiration: p.expiration.get().inner().map(|x| { - DateTime::from_timestamp_millis(*x as i64) + DateTime::from_timestamp_millis(x.0 as i64) .expect("invalid timestamp stored in db") }), expired: p.is_expired(now), @@ -218,7 +219,7 @@ async fn key_info_results( DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db") }), expiration: key_state.expiration.get().inner().map(|x| { - DateTime::from_timestamp_millis(*x as i64).expect("invalid timestamp stored in db") + DateTime::from_timestamp_millis(x.0 as i64).expect("invalid timestamp stored in db") }), expired: key_state.is_expired(now_msec()), access_key_id: key.key_id.clone(), @@ -283,7 +284,7 @@ fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) -> Result<(), if let Some(expiration) = updates.expiration { key_state .expiration - .update(Some(expiration.timestamp_millis() as u64).into()); + .update(Some(ExpirationTime(expiration.timestamp_millis() as u64)).into()); } if updates.never_expires { key_state.expiration.update(None.into()); diff --git a/src/model/admin_token_table.rs b/src/model/admin_token_table.rs index 21ce2c08..2159bdb6 100644 --- a/src/model/admin_token_table.rs +++ b/src/model/admin_token_table.rs @@ -8,6 +8,7 @@ use garage_table::{EmptyKey, Entry, TableSchema}; pub use crate::key_table::KeyFilter; mod v2 { + use crate::permission::ExpirationTime; use garage_util::crdt; use serde::{Deserialize, Serialize}; @@ -35,7 +36,7 @@ mod v2 { pub name: crdt::Lww, /// The optional time of expiration of the token - pub expiration: crdt::Lww>, + pub expiration: crdt::Lww>, /// The scope of the token, i.e. list of authorized admin API calls pub scope: crdt::Lww, @@ -149,7 +150,7 @@ impl AdminApiTokenParams { pub fn is_expired(&self, ts_now: u64) -> bool { match self.expiration.get().inner() { None => false, - Some(exp) => ts_now >= *exp, + Some(exp) => ts_now >= exp.0, } } diff --git a/src/model/key_table.rs b/src/model/key_table.rs index da0dcb7d..13ad4c8e 100644 --- a/src/model/key_table.rs +++ b/src/model/key_table.rs @@ -51,6 +51,7 @@ mod v08 { mod v2 { use crate::permission::BucketKeyPerm; + use crate::permission::ExpirationTime; use garage_util::crdt; use garage_util::data::Uuid; use serde::{Deserialize, Serialize}; @@ -79,7 +80,7 @@ mod v2 { /// Name for the key pub name: crdt::Lww, /// The optional time of expiration of the key - pub expiration: crdt::Lww>, + pub expiration: crdt::Lww>, /// Flag to allow users having this key to create buckets pub allow_create_bucket: crdt::Lww, @@ -231,7 +232,7 @@ impl KeyParams { pub fn is_expired(&self, ts_now: u64) -> bool { match self.expiration.get().inner() { None => false, - Some(exp) => ts_now >= *exp, + Some(exp) => ts_now >= exp.0, } } } diff --git a/src/model/permission.rs b/src/model/permission.rs index ed9d9c68..6bb53e17 100644 --- a/src/model/permission.rs +++ b/src/model/permission.rs @@ -63,3 +63,15 @@ impl Crdt for BucketKeyPerm { } } } + +/// Expiration date for a key or token +#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde(transparent)] +pub struct ExpirationTime(pub u64); + +impl Crdt for ExpirationTime { + fn merge(&mut self, other: &Self) { + self.0 = std::cmp::min(self.0, other.0); + } +}