Browse Source

Change OAuth2 expiration handling to use i64 for timestamps

pull/6388/head
Henning 1 week ago
parent
commit
8db5c43134
  1. 8
      src/api/admin.rs
  2. 14
      src/mail.rs

8
src/api/admin.rs

@ -106,7 +106,7 @@ static CAN_BACKUP: LazyLock<bool> =
static CAN_BACKUP: LazyLock<bool> = LazyLock::new(|| false); static CAN_BACKUP: LazyLock<bool> = LazyLock::new(|| false);
// OAuth2 state storage for CSRF protection (state -> expiration timestamp) // OAuth2 state storage for CSRF protection (state -> expiration timestamp)
static OAUTH2_STATES: LazyLock<RwLock<HashMap<String, u64>>> = LazyLock::new(|| RwLock::new(HashMap::new())); static OAUTH2_STATES: LazyLock<RwLock<HashMap<String, i64>>> = LazyLock::new(|| RwLock::new(HashMap::new()));
#[get("/")] #[get("/")]
fn admin_disabled() -> &'static str { fn admin_disabled() -> &'static str {
@ -372,9 +372,7 @@ fn oauth2_authorize(_token: AdminToken) -> Result<Redirect, Error> {
let state = crate::crypto::encode_random_bytes::<32>(&BASE64URL_NOPAD); let state = crate::crypto::encode_random_bytes::<32>(&BASE64URL_NOPAD);
// Store state with expiration (10 minutes from now) // Store state with expiration (10 minutes from now)
// Multiple calls to Utc::now().timestamp() can return a negative value if the system time is set before the UNIX epoch. let now = Utc::now().timestamp();
// While extremely rare in practice, we handle this by using unwrap_or_default() to prevent huge values when casting to u64.
let now = u64::try_from(Utc::now().timestamp()).unwrap_or_default();
let expiration = now + 600; let expiration = now + 600;
OAUTH2_STATES.write().unwrap().insert(state.clone(), expiration); OAUTH2_STATES.write().unwrap().insert(state.clone(), expiration);
@ -433,7 +431,7 @@ async fn oauth2_callback(
// Validate state token // Validate state token
let valid_state = { let valid_state = {
let states = OAUTH2_STATES.read().unwrap(); let states = OAUTH2_STATES.read().unwrap();
let now = u64::try_from(Utc::now().timestamp()).unwrap_or_default(); let now = Utc::now().timestamp();
states.get(&state).is_some_and(|&exp| exp > now) states.get(&state).is_some_and(|&exp| exp > now)
}; };

14
src/mail.rs

@ -31,7 +31,7 @@ use crate::http_client::make_http_request;
pub struct OAuth2Token { pub struct OAuth2Token {
access_token: String, access_token: String,
refresh_token: Option<String>, refresh_token: Option<String>,
expires_at: Option<u64>, expires_at: Option<i64>,
token_type: String, token_type: String,
} }
@ -39,7 +39,7 @@ pub struct OAuth2Token {
struct TokenRefreshResponse { struct TokenRefreshResponse {
access_token: String, access_token: String,
refresh_token: Option<String>, refresh_token: Option<String>,
expires_in: Option<u64>, expires_in: Option<i64>,
token_type: String, token_type: String,
} }
@ -78,11 +78,7 @@ pub async fn refresh_oauth2_token() -> Result<OAuth2Token, Error> {
Err(e) => err!(format!("OAuth2 Token Parse Error: {e}")), Err(e) => err!(format!("OAuth2 Token Parse Error: {e}")),
}; };
let expires_at = token_response.expires_in.map(|expires_in| { let expires_at = token_response.expires_in.map(|expires_in| Utc::now().timestamp() + expires_in);
// Multiple calls to Utc::now().timestamp() can return a negative value if the system time is set before the UNIX epoch.
// While extremely rare in practice, we handle this by using unwrap_or_default() to prevent huge values when casting to u64.
u64::try_from(Utc::now().timestamp()).unwrap_or_default() + expires_in
});
let new_token = OAuth2Token { let new_token = OAuth2Token {
access_token: token_response.access_token, access_token: token_response.access_token,
@ -106,7 +102,7 @@ async fn get_valid_oauth2_token() -> Result<OAuth2Token, Error> {
if let Some(token) = token_cache.as_ref() { if let Some(token) = token_cache.as_ref() {
// Check if token is still valid (with 5 min buffer) // Check if token is still valid (with 5 min buffer)
if let Some(expires_at) = token.expires_at { if let Some(expires_at) = token.expires_at {
let now = u64::try_from(Utc::now().timestamp()).unwrap_or_default(); let now = Utc::now().timestamp();
if now + 300 < expires_at { if now + 300 < expires_at {
return Ok(token.clone()); return Ok(token.clone());
} }
@ -120,7 +116,7 @@ async fn get_valid_oauth2_token() -> Result<OAuth2Token, Error> {
// Double check // Double check
if let Some(token) = token_cache.as_ref() { if let Some(token) = token_cache.as_ref() {
if let Some(expires_at) = token.expires_at { if let Some(expires_at) = token.expires_at {
let now = u64::try_from(Utc::now().timestamp()).unwrap_or_default(); let now = Utc::now().timestamp();
if now + 300 < expires_at { if now + 300 < expires_at {
return Ok(token.clone()); return Ok(token.clone());
} }

Loading…
Cancel
Save