From e6b91c1ce4ad979fe6387f91104a52ae57a67eb9 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 11 Sep 2023 11:16:12 +0200 Subject: [PATCH] data-model: make the access token expiration optional --- crates/data-model/src/compat/session.rs | 24 ++++++++++++-- crates/data-model/src/oauth2/session.rs | 28 +++++++++++----- crates/data-model/src/tokens.rs | 33 +++++++++++++++++-- .../graphql/src/mutations/oauth2_session.rs | 6 ++-- crates/handlers/src/oauth2/introspection.rs | 2 +- crates/storage-pg/src/oauth2/access_token.rs | 4 +-- 6 files changed, 79 insertions(+), 18 deletions(-) diff --git a/crates/data-model/src/compat/session.rs b/crates/data-model/src/compat/session.rs index 1132e02e..3d254e17 100644 --- a/crates/data-model/src/compat/session.rs +++ b/crates/data-model/src/compat/session.rs @@ -29,7 +29,7 @@ pub enum CompatSessionState { } impl CompatSessionState { - /// Returns `true` if the compta session state is [`Valid`]. + /// Returns `true` if the compat session state is [`Valid`]. /// /// [`Valid`]: CompatSessionState::Valid #[must_use] @@ -37,7 +37,7 @@ impl CompatSessionState { matches!(self, Self::Valid) } - /// Returns `true` if the compta session state is [`Finished`]. + /// Returns `true` if the compat session state is [`Finished`]. /// /// [`Finished`]: CompatSessionState::Finished #[must_use] @@ -45,6 +45,17 @@ impl CompatSessionState { matches!(self, Self::Finished { .. }) } + /// Transitions the session state to [`Finished`]. + /// + /// # Parameters + /// + /// * `finished_at` - The time at which the session was finished. + /// + /// # Errors + /// + /// Returns an error if the session state is already [`Finished`]. + /// + /// [`Finished`]: CompatSessionState::Finished pub fn finish(self, finished_at: DateTime) -> Result { match self { Self::Valid => Ok(Self::Finished { finished_at }), @@ -80,6 +91,15 @@ impl std::ops::Deref for CompatSession { } impl CompatSession { + /// Marks the session as finished. + /// + /// # Parameters + /// + /// * `finished_at` - The time at which the session was finished. + /// + /// # Errors + /// + /// Returns an error if the session is already finished. pub fn finish(mut self, finished_at: DateTime) -> Result { self.state = self.state.finish(finished_at)?; Ok(self) diff --git a/crates/data-model/src/oauth2/session.rs b/crates/data-model/src/oauth2/session.rs index ae55db4a..68cbd79f 100644 --- a/crates/data-model/src/oauth2/session.rs +++ b/crates/data-model/src/oauth2/session.rs @@ -19,14 +19,6 @@ use ulid::Ulid; use crate::InvalidTransitionError; -trait T { - type State; -} - -impl T for Session { - type State = SessionState; -} - #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] pub enum SessionState { #[default] @@ -53,6 +45,17 @@ impl SessionState { matches!(self, Self::Finished { .. }) } + /// Transitions the session state to [`Finished`]. + /// + /// # Parameters + /// + /// * `finished_at` - The time at which the session was finished. + /// + /// # Errors + /// + /// Returns an error if the session state is already [`Finished`]. + /// + /// [`Finished`]: SessionState::Finished pub fn finish(self, finished_at: DateTime) -> Result { match self { Self::Valid => Ok(Self::Finished { finished_at }), @@ -81,6 +84,15 @@ impl std::ops::Deref for Session { } impl Session { + /// Marks the session as finished. + /// + /// # Parameters + /// + /// * `finished_at` - The time at which the session was finished. + /// + /// # Errors + /// + /// Returns an error if the session is already finished. pub fn finish(mut self, finished_at: DateTime) -> Result { self.state = self.state.finish(finished_at)?; Ok(self) diff --git a/crates/data-model/src/tokens.rs b/crates/data-model/src/tokens.rs index 646f8d44..62ac542f 100644 --- a/crates/data-model/src/tokens.rs +++ b/crates/data-model/src/tokens.rs @@ -62,7 +62,7 @@ pub struct AccessToken { pub session_id: Ulid, pub access_token: String, pub created_at: DateTime, - pub expires_at: DateTime, + pub expires_at: Option>, } impl AccessToken { @@ -71,11 +71,40 @@ impl AccessToken { self.id.to_string() } + /// Whether the access token is valid, i.e. not revoked and not expired + /// + /// # Parameters + /// + /// * `now` - The current time #[must_use] pub fn is_valid(&self, now: DateTime) -> bool { - self.state.is_valid() && self.expires_at > now + self.state.is_valid() && !self.is_expired(now) } + /// Whether the access token is expired + /// + /// Always returns `false` if the access token does not have an expiry time. + /// + /// # Parameters + /// + /// * `now` - The current time + #[must_use] + pub fn is_expired(&self, now: DateTime) -> bool { + match self.expires_at { + Some(expires_at) => expires_at < now, + None => false, + } + } + + /// Mark the access token as revoked + /// + /// # Parameters + /// + /// * `revoked_at` - The time at which the access token was revoked + /// + /// # Errors + /// + /// Returns an error if the access token is already revoked pub fn revoke(mut self, revoked_at: DateTime) -> Result { self.state = self.state.revoke(revoked_at)?; Ok(self) diff --git a/crates/graphql/src/mutations/oauth2_session.rs b/crates/graphql/src/mutations/oauth2_session.rs index fb7126fa..ba169067 100644 --- a/crates/graphql/src/mutations/oauth2_session.rs +++ b/crates/graphql/src/mutations/oauth2_session.rs @@ -187,7 +187,9 @@ impl OAuth2SessionMutations { .add(&mut rng, &clock, &session, access_token, ttl) .await?; - let refresh_token = if !permanent { + let refresh_token = if permanent { + None + } else { let refresh_token = TokenType::RefreshToken.generate(&mut rng); let refresh_token = repo @@ -196,8 +198,6 @@ impl OAuth2SessionMutations { .await?; Some(refresh_token) - } else { - None }; Ok(CreateOAuth2SessionPayload { diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index 8a597367..e4bf02f4 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -209,7 +209,7 @@ pub(crate) async fn post( client_id: Some(session.client_id.to_string()), username, token_type: Some(OAuthTokenTypeHint::AccessToken), - exp: Some(token.expires_at), + exp: token.expires_at, iat: Some(token.created_at), nbf: Some(token.created_at), sub, diff --git a/crates/storage-pg/src/oauth2/access_token.rs b/crates/storage-pg/src/oauth2/access_token.rs index 758fdf41..1161dbe5 100644 --- a/crates/storage-pg/src/oauth2/access_token.rs +++ b/crates/storage-pg/src/oauth2/access_token.rs @@ -59,7 +59,7 @@ impl From for AccessToken { session_id: value.oauth2_session_id.into(), access_token: value.access_token, created_at: value.created_at, - expires_at: value.expires_at, + expires_at: Some(value.expires_at), } } } @@ -177,7 +177,7 @@ impl<'c> OAuth2AccessTokenRepository for PgOAuth2AccessTokenRepository<'c> { access_token, session_id: session.id, created_at, - expires_at, + expires_at: Some(expires_at), }) }