diff --git a/src/admin/token/commands.rs b/src/admin/token/commands.rs index 4ff74a200..5a00457e2 100644 --- a/src/admin/token/commands.rs +++ b/src/admin/token/commands.rs @@ -6,18 +6,24 @@ use service::registration_tokens::TokenExpires; #[admin_command] pub(super) async fn issue_token(&self, expires: super::TokenExpires) -> Result { let expires = { - if expires.immortal { - None - } else if let Some(max_uses) = expires.max_uses { - Some(TokenExpires::AfterUses(max_uses)) - } else if expires.once { - Some(TokenExpires::AfterUses(1)) - } else if let Some(max_age) = expires + let max_uses = expires.max_uses; + let max_age = expires .max_age .as_deref() .map(|max_age| utils::time::timepoint_from_now(utils::time::parse_duration(max_age)?)) - .transpose()? + .transpose()?; + + if expires.immortal { + None + } else if let Some(max_uses) = max_uses + && let Some(max_age) = max_age { + Some(TokenExpires::AfterTimeOrUses { max_uses, max_age }) + } else if let Some(max_uses) = max_uses { + Some(TokenExpires::AfterUses(max_uses)) + } else if expires.once { + Some(TokenExpires::AfterUses(1)) + } else if let Some(max_age) = max_age { Some(TokenExpires::AfterTime(max_age)) } else { unreachable!(); diff --git a/src/admin/token/mod.rs b/src/admin/token/mod.rs index 9265a2f5f..1cdb5b2a3 100644 --- a/src/admin/token/mod.rs +++ b/src/admin/token/mod.rs @@ -29,23 +29,23 @@ pub enum TokenCommand { } #[derive(Debug, Args)] -#[group(required = true, multiple = false)] +#[group(required = true, multiple = true)] pub struct TokenExpires { /// The maximum number of times this token is allowed to be used before it /// expires. - #[arg(long)] + #[arg(long, conflicts_with_all = ["immortal", "once"])] max_uses: Option, /// The maximum age of this token (e.g. 30s, 5m, 7d). It will expire after /// this much time has passed. - #[arg(long)] + #[arg(long, conflicts_with_all = ["immortal", "once"])] max_age: Option, /// This token will never expire. - #[arg(long)] + #[arg(long, conflicts_with_all = ["once", "max_uses", "max_age"])] immortal: bool, /// A shortcut for `--max-uses 1`. - #[arg(long)] + #[arg(long, conflicts_with_all = ["immortal", "max_uses", "max_age"])] once: bool, -} +} \ No newline at end of file diff --git a/src/service/registration_tokens/data.rs b/src/service/registration_tokens/data.rs index ee2aee437..886f58805 100644 --- a/src/service/registration_tokens/data.rs +++ b/src/service/registration_tokens/data.rs @@ -41,6 +41,8 @@ impl DatabaseTokenInfo { expiry_time >= now }, + | Some(TokenExpires::AfterTimeOrUses { max_uses, max_age }) => + self.uses < max_uses && max_age >= SystemTime::now(), | None => true, } } @@ -63,12 +65,16 @@ impl std::fmt::Display for DatabaseTokenInfo { pub enum TokenExpires { AfterUses(u64), AfterTime(SystemTime), + AfterTimeOrUses { + max_uses: u64, + max_age: SystemTime, + }, } impl std::fmt::Display for TokenExpires { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - | Self::AfterUses(max_uses) => write!(f, "Expires after {max_uses} uses"), + | Self::AfterUses(max_uses) => write!(f, "Expires after {max_uses} use(s)"), | Self::AfterTime(max_age) => { let now = SystemTime::now(); let formatted_expiry = utils::time::format(*max_age, "%+"); @@ -82,6 +88,23 @@ impl std::fmt::Display for TokenExpires { | Err(_) => write!(f, "Expired at {formatted_expiry}"), } }, + | Self::AfterTimeOrUses { max_uses, max_age } => { + if *max_uses <= 0 { + return write!(f, "Already expired (uses exhausted)"); + } + + let now = SystemTime::now(); + let formatted_expiry = utils::time::format(*max_age, "%+"); + + match max_age.duration_since(now) { + | Ok(duration) => write!( + f, + "Expires after {max_uses} use(s) or in {} ({formatted_expiry})", + utils::time::pretty(duration) + ), + | Err(_) => write!(f, "Expired at {formatted_expiry}"), + } + }, } } }