diff --git a/crates/handlers/src/admin/v1/personal_sessions/list.rs b/crates/handlers/src/admin/v1/personal_sessions/list.rs index df92c33ed..2f91d10bf 100644 --- a/crates/handlers/src/admin/v1/personal_sessions/list.rs +++ b/crates/handlers/src/admin/v1/personal_sessions/list.rs @@ -75,6 +75,10 @@ pub struct FilterParams { /// Filter by access token expiry date #[serde(rename = "filter[expires_after]")] expires_after: Option>, + + /// Filter by whether the access token has an expiry time + #[serde(rename = "filter[expires]")] + expires: Option, } impl std::fmt::Display for FilterParams { @@ -113,6 +117,10 @@ impl std::fmt::Display for FilterParams { )?; sep = '&'; } + if let Some(expires) = self.expires { + write!(f, "{sep}filter[expires]={}", expires)?; + sep = '&'; + } let _ = sep; Ok(()) @@ -270,6 +278,12 @@ pub async fn handler( filter }; + let filter = if let Some(expires) = params.expires { + filter.with_expires(expires) + } else { + filter + }; + let response = match include_count { IncludeCount::True => { let page = repo.personal_session().list(filter, pagination).await?; @@ -514,6 +528,11 @@ mod tests { &["01FSHN9AG0YQYAR04VCYTHJ8SK", "01FSPT2RG08Y11Y5BM4VZ4CN8K"], ), ("filter[status]=revoked", &["01FSM7P1G0VBGAMK9D9QMGQ5MY"]), + ( + "filter[expires]=true", + &["01FSHN9AG0YQYAR04VCYTHJ8SK", "01FSPT2RG08Y11Y5BM4VZ4CN8K"], + ), + ("filter[expires]=false", &["01FSM7P1G0VBGAMK9D9QMGQ5MY"]), ]; for (filter, expected_ids) in filters_and_expected { diff --git a/crates/storage-pg/src/personal/session.rs b/crates/storage-pg/src/personal/session.rs index c3851c05e..540315e8b 100644 --- a/crates/storage-pg/src/personal/session.rs +++ b/crates/storage-pg/src/personal/session.rs @@ -567,5 +567,15 @@ impl Filter for PersonalSessionFilter<'_> { Expr::col((PersonalAccessTokens::Table, PersonalAccessTokens::ExpiresAt)) .gt(expires_after) })) + .add_option(self.expires().map(|expires| { + let column = + Expr::col((PersonalAccessTokens::Table, PersonalAccessTokens::ExpiresAt)); + + if expires { + column.is_not_null() + } else { + column.is_null() + } + })) } } diff --git a/crates/storage/src/personal/session.rs b/crates/storage/src/personal/session.rs index 718d86c99..f4718650b 100644 --- a/crates/storage/src/personal/session.rs +++ b/crates/storage/src/personal/session.rs @@ -156,6 +156,7 @@ pub struct PersonalSessionFilter<'a> { last_active_after: Option>, expires_before: Option>, expires_after: Option>, + expires: Option, } /// Filter for what state a personal session is in. @@ -332,4 +333,20 @@ impl<'a> PersonalSessionFilter<'a> { pub fn expires_after(&self) -> Option> { self.expires_after } + + /// Only return sessions whose access tokens have, or don't have, + /// an expiry time set + #[must_use] + pub fn with_expires(mut self, expires: bool) -> Self { + self.expires = Some(expires); + self + } + + /// Get the expires filter + /// + /// Returns [`None`] if no expires filter was set + #[must_use] + pub fn expires(&self) -> Option { + self.expires + } }