From e9640876c764dab04645f57b8ef26cfa9d3c533d Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 1 Oct 2025 14:26:14 -0700 Subject: [PATCH 1/5] token rotation --- crates/turborepo-auth/src/auth/login.rs | 43 ++++--- crates/turborepo-auth/src/auth/mod.rs | 88 +++++++++++++ crates/turborepo-auth/src/auth/sso.rs | 42 +++--- crates/turborepo-auth/src/error.rs | 2 + crates/turborepo-auth/src/lib.rs | 148 ++++++++++++++++++++++ crates/turborepo-lib/src/commands/link.rs | 32 +++-- 6 files changed, 308 insertions(+), 47 deletions(-) diff --git a/crates/turborepo-auth/src/auth/login.rs b/crates/turborepo-auth/src/auth/login.rs index d223e96f0d43a..f5cae2fde0a21 100644 --- a/crates/turborepo-auth/src/auth/login.rs +++ b/crates/turborepo-auth/src/auth/login.rs @@ -7,7 +7,7 @@ use tracing::{debug, warn}; use turborepo_api_client::{CacheClient, Client, TokenClient}; use turborepo_ui::{BOLD, ColorConfig, start_spinner}; -use crate::{LoginOptions, Token, auth::extract_vercel_token, error, ui}; +use crate::{LoginOptions, Token, error, ui}; const DEFAULT_HOST_NAME: &str = "127.0.0.1"; const DEFAULT_PORT: u16 = 9789; @@ -61,24 +61,31 @@ pub async fn login( { return Ok(token); } - // If the user is logging into Vercel, check for an existing `vc` token. + // If the user is logging into Vercel, check for an existing `vc` token + // with automatic refresh if expired. } else if login_url_configuration.contains("vercel.com") { - // The extraction can return an error, but we don't want to fail the login if - // the token is not found. - if let Ok(Some(token)) = extract_vercel_token() { - debug!("found existing Vercel token"); - let token = Token::existing(token); - if token - .is_valid( - api_client, - Some(valid_token_callback( - "Existing Vercel token found!", - color_config, - )), - ) - .await? - { - return Ok(token); + match crate::auth::get_token_with_refresh().await { + Ok(Some(token_str)) => { + debug!("found existing Vercel token (possibly refreshed)"); + let token = Token::existing(token_str); + if token + .is_valid( + api_client, + Some(valid_token_callback( + "Existing Vercel token found!", + color_config, + )), + ) + .await? + { + return Ok(token); + } + } + Ok(None) => { + debug!("no valid token found"); + } + Err(e) => { + debug!("error getting token with refresh: {}", e); } } } diff --git a/crates/turborepo-auth/src/auth/mod.rs b/crates/turborepo-auth/src/auth/mod.rs index 4005d9b0e9847..620a3d7b1af7d 100644 --- a/crates/turborepo-auth/src/auth/mod.rs +++ b/crates/turborepo-auth/src/auth/mod.rs @@ -74,3 +74,91 @@ fn extract_vercel_token() -> Result, Error> { Ok(serde_json::from_str::(&contents)?.token) } + +/// Attempts to get a valid token with automatic refresh if expired. +/// Falls back to turborepo/config.json if refresh fails. +pub async fn get_token_with_refresh() -> Result, Error> { + use crate::{TURBO_TOKEN_DIR, TURBO_TOKEN_FILE, Token}; + + let vercel_config_dir = match turborepo_dirs::vercel_config_dir()? { + Some(dir) => dir, + None => { + tracing::debug!("No Vercel config directory found"); + return Ok(None); + } + }; + + let auth_path = vercel_config_dir.join_components(&[VERCEL_TOKEN_DIR, VERCEL_TOKEN_FILE]); + tracing::debug!("Reading auth tokens from: {}", auth_path); + + // Try to read auth.json with token and refresh token + let auth_tokens = Token::from_auth_file(&auth_path)?; + tracing::debug!( + "Auth tokens loaded - has token: {}, has refresh: {}, expires_at: {:?}", + auth_tokens.token.is_some(), + auth_tokens.refresh_token.is_some(), + auth_tokens.expires_at + ); + + println!("hey hiiiii howdy"); + // If we have a token + if let Some(token) = &auth_tokens.token { + // Check if token is expired + if auth_tokens.is_expired() { + println!("hey hi howdy"); + // Try to refresh the token + if auth_tokens.refresh_token.is_some() { + match auth_tokens.refresh_token().await { + Ok(new_tokens) => { + tracing::info!("Successfully refreshed token"); + // Write the new tokens back to auth.json + if let Err(e) = new_tokens.write_to_auth_file(&auth_path) { + tracing::warn!("Failed to write refreshed token to auth.json: {}", e); + } else { + tracing::info!("Updated auth.json with new tokens"); + } + return Ok(new_tokens.token); + } + Err(e) => { + tracing::warn!("Failed to refresh token: {}", e); + // Fall through to try turborepo/config.json + } + } + } + + // Token expired and refresh failed, try turborepo/config.json + tracing::info!("Attempting to fall back to turborepo/config.json"); + if let Ok(Some(config_dir)) = turborepo_dirs::config_dir() { + let turbo_config_path = + config_dir.join_components(&[TURBO_TOKEN_DIR, TURBO_TOKEN_FILE]); + tracing::debug!("Checking for fallback token at: {}", turbo_config_path); + if let Ok(turbo_token) = Token::from_file(&turbo_config_path) { + tracing::info!("Found valid fallback token in turborepo/config.json"); + return Ok(Some(turbo_token.into_inner().to_string())); + } else { + tracing::debug!("No valid token found in turborepo/config.json"); + } + } else { + tracing::debug!("Could not locate config directory for fallback"); + } + + // No valid fallback token found + tracing::warn!("No valid token found after refresh attempt and fallback"); + Ok(None) + } else { + // Token is not expired, use it + tracing::debug!("Token is still valid, using existing token"); + Ok(Some(token.clone())) + } + } else { + // No token in auth.json, try turborepo/config.json + if let Ok(Some(config_dir)) = turborepo_dirs::config_dir() { + let turbo_config_path = + config_dir.join_components(&[TURBO_TOKEN_DIR, TURBO_TOKEN_FILE]); + if let Ok(turbo_token) = Token::from_file(&turbo_config_path) { + return Ok(Some(turbo_token.into_inner().to_string())); + } + } + Ok(None) + } +} diff --git a/crates/turborepo-auth/src/auth/sso.rs b/crates/turborepo-auth/src/auth/sso.rs index 01d51d3865e72..063d7f6887dda 100644 --- a/crates/turborepo-auth/src/auth/sso.rs +++ b/crates/turborepo-auth/src/auth/sso.rs @@ -6,7 +6,7 @@ use tracing::warn; use turborepo_api_client::{CacheClient, Client, TokenClient}; use turborepo_ui::{BOLD, ColorConfig, start_spinner}; -use crate::{Error, LoginOptions, Token, auth::extract_vercel_token, error, ui}; +use crate::{Error, LoginOptions, Token, error, ui}; const DEFAULT_HOST_NAME: &str = "127.0.0.1"; const DEFAULT_PORT: u16 = 9789; @@ -70,23 +70,29 @@ pub async fn sso_login( return Ok(token); } // No existing turbo token found. If the user is logging into Vercel, - // check for an existing `vc` token with correct scope. - } else if login_url_configuration.contains("vercel.com") - && let Ok(Some(token)) = extract_vercel_token() - { - let token = Token::existing(token); - if token - .is_valid_sso( - api_client, - sso_team, - Some(valid_token_callback( - &format!("Existing Vercel token for {sso_team} found!"), - color_config, - )), - ) - .await? - { - return Ok(token); + // check for an existing token with automatic refresh if expired. + } else if login_url_configuration.contains("vercel.com") { + match crate::auth::get_token_with_refresh().await { + Ok(Some(token_str)) => { + let token = Token::existing(token_str); + if token + .is_valid_sso( + api_client, + sso_team, + Some(valid_token_callback( + &format!("Existing Vercel token for {sso_team} found!"), + color_config, + )), + ) + .await? + { + return Ok(token); + } + } + Ok(None) => {} + Err(e) => { + tracing::debug!("error getting token with refresh: {}", e); + } } } } diff --git a/crates/turborepo-auth/src/error.rs b/crates/turborepo-auth/src/error.rs index 9ec40bc0df58a..5e4a75b98681b 100644 --- a/crates/turborepo-auth/src/error.rs +++ b/crates/turborepo-auth/src/error.rs @@ -11,6 +11,8 @@ pub enum Error { SerdeError(#[from] serde_json::Error), #[error(transparent)] APIError(#[from] turborepo_api_client::Error), + #[error(transparent)] + ReqwestError(#[from] reqwest::Error), #[error( "`loginUrl` is configured to \"{value}\", but cannot be a base URL. This happens in \ diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 09befb45c65c7..70222552e7af4 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -27,6 +27,22 @@ pub const VERCEL_TOKEN_FILE: &str = "auth.json"; pub const TURBO_TOKEN_DIR: &str = "turborepo"; pub const TURBO_TOKEN_FILE: &str = "config.json"; +const VERCEL_OAUTH_CLIENT_ID: &str = "cl_HYyOPBNtFMfHhaUn9L4QPfTZz6TP47bp"; +const VERCEL_OAUTH_TOKEN_URL: &str = "https://vercel.com/api/login/oauth/token"; + +#[derive(Debug, Clone)] +pub struct AuthTokens { + pub token: Option, + pub refresh_token: Option, + pub expires_at: Option, +} + +#[derive(Debug, serde::Deserialize)] +struct OAuthTokenResponse { + access_token: String, + refresh_token: String, +} + /// Token. /// /// It's the result of a successful login or an existing token. This acts as @@ -78,6 +94,38 @@ impl Token { } } + /// Reads token, refresh token, and expiration from auth.json + pub fn from_auth_file(path: &AbsoluteSystemPath) -> Result { + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct AuthWrapper { + token: Option, + refresh_token: Option, + expires_at: Option, + } + + match path.read_existing_to_string()? { + Some(content) => { + let wrapper = serde_json::from_str::(&content).map_err(|err| { + Error::InvalidTokenFileFormat { + path: path.to_string(), + source: err, + } + })?; + Ok(AuthTokens { + token: wrapper.token, + refresh_token: wrapper.refresh_token, + expires_at: wrapper.expires_at, + }) + } + None => Ok(AuthTokens { + token: None, + refresh_token: None, + expires_at: None, + }), + } + } + /// Checks if the token is still valid. The checks ran are: /// 1. If the token is active. /// 2. If the token has access to the cache. @@ -286,6 +334,14 @@ fn current_unix_time() -> u128 { .as_millis() } +fn current_unix_time_secs() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() +} + // As of the time of writing, this should always be true, since a token that // isn't active returns an error when fetching metadata for the token. fn is_token_active(metadata: &ResponseTokenMetadata, current_time: u128) -> bool { @@ -305,6 +361,98 @@ fn is_token_active(metadata: &ResponseTokenMetadata, current_time: u128) -> bool all_scopes_active && (active_at <= current_time) } +impl AuthTokens { + /// Checks if the access token has expired based on expiresAt field + pub fn is_expired(&self) -> bool { + if let Some(expires_at) = self.expires_at { + let current_time = current_unix_time_secs(); + println!("{}, {}", current_time, expires_at); + current_time >= expires_at + } else { + false + } + } + + /// Attempts to refresh the access token using the refresh token + pub async fn refresh_token(&self) -> Result { + let refresh_token = self + .refresh_token + .as_ref() + .ok_or_else(|| Error::TokenNotFound)?; + + tracing::debug!( + "Attempting to refresh token with refresh_token: {}", + refresh_token + ); + + let client = reqwest::Client::new(); + let params = [ + ("refresh_token", refresh_token.as_str()), + ("grant_type", "refresh_token"), + ("client_id", VERCEL_OAUTH_CLIENT_ID), + ]; + + let response = client + .post(VERCEL_OAUTH_TOKEN_URL) + .form(¶ms) + .send() + .await?; + + tracing::debug!("{}", response.url()); + + let status = response.status(); + tracing::debug!("Token refresh response status: {}", status); + + if !status.is_success() { + let body = response + .text() + .await + .unwrap_or_else(|_| "Unable to read response body".to_string()); + tracing::error!("Token refresh failed with status {}: {}", status, body); + return Err(Error::FailedToGetToken); + } + + // Get the response text first for debugging + let response_text = response.text().await?; + tracing::debug!("Token refresh response body: {}", response_text); + + let oauth_response: OAuthTokenResponse = + serde_json::from_str(&response_text).map_err(|e| { + tracing::error!( + "Failed to parse OAuth response: {}. Body was: {}", + e, + response_text + ); + e + })?; + tracing::info!("Token refresh successful"); + + Ok(AuthTokens { + token: Some(oauth_response.access_token), + refresh_token: Some(oauth_response.refresh_token), + expires_at: Some(current_unix_time_secs() + 8 * 60 * 60), // 8 hours from now + }) + } + + /// Writes the auth tokens to the auth.json file + pub fn write_to_auth_file(&self, path: &AbsoluteSystemPath) -> Result<(), Error> { + use serde_json::json; + + let content = json!({ + "// Note": "This is your Vercel credentials file. DO NOT SHARE!", + "// Docs": "https://vercel.com/docs/projects/project-configuration/global-configuration#auth.json", + "token": self.token, + "refreshToken": self.refresh_token, + "expiresAt": self.expires_at, + }); + + let json_string = serde_json::to_string_pretty(&content)?; + path.ensure_dir()?; + path.create_with_contents(json_string)?; + Ok(()) + } +} + #[cfg(test)] mod tests { use std::backtrace::Backtrace; diff --git a/crates/turborepo-lib/src/commands/link.rs b/crates/turborepo-lib/src/commands/link.rs index c2e8ed2d8a6ff..a7a3ac018854d 100644 --- a/crates/turborepo-lib/src/commands/link.rs +++ b/crates/turborepo-lib/src/commands/link.rs @@ -19,7 +19,7 @@ use thiserror::Error; use turborepo_api_client::{CacheClient, Client}; #[cfg(not(test))] use turborepo_ui::CYAN; -use turborepo_ui::{DialoguerTheme, BOLD, GREY}; +use turborepo_ui::{BOLD, DialoguerTheme, GREY}; use turborepo_vercel_api::{CachingStatus, Team}; use crate::{ @@ -153,14 +153,24 @@ pub async fn link( let homedir = homedir_path.to_string_lossy(); let repo_root_with_tilde = base.repo_root.to_string().replacen(&*homedir, "~", 1); let api_client = base.api_client()?; - let token = base - .opts() - .api_client_opts - .token - .as_deref() - .ok_or_else(|| Error::TokenNotFound { - command: base.color_config.apply(BOLD.apply_to("`npx turbo login`")), - })?; + + // Always try to get a valid token with automatic refresh if expired + let token = match turborepo_auth::get_token_with_refresh().await { + Ok(Some(refreshed_token)) => { + // Store the refreshed token temporarily for this command + Box::leak(refreshed_token.into_boxed_str()) + } + Ok(None) | Err(_) => { + // Fall back to the token from config/CLI if refresh logic didn't work + base.opts() + .api_client_opts + .token + .as_deref() + .ok_or_else(|| Error::TokenNotFound { + command: base.color_config.apply(BOLD.apply_to("`npx turbo login`")), + })? + } + }; println!( "\n{}\n\n{}\n\nFor more information, visit: {}\n", @@ -385,10 +395,10 @@ mod test { use turborepo_vercel_api_mock::start_test_server; use crate::{ - commands::{link, CommandBase}, + Args, + commands::{CommandBase, link}, config::TurborepoConfigBuilder, opts::Opts, - Args, }; #[tokio::test] From f9e758eed4cae89891afa1187205096f2fa92a35 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 1 Oct 2025 14:36:10 -0700 Subject: [PATCH 2/5] remove junk --- crates/turborepo-auth/src/auth/login.rs | 12 ++--- crates/turborepo-auth/src/auth/mod.rs | 65 ++----------------------- crates/turborepo-auth/src/auth/sso.rs | 4 +- crates/turborepo-auth/src/lib.rs | 31 ++---------- 4 files changed, 11 insertions(+), 101 deletions(-) diff --git a/crates/turborepo-auth/src/auth/login.rs b/crates/turborepo-auth/src/auth/login.rs index f5cae2fde0a21..789aff85d816e 100644 --- a/crates/turborepo-auth/src/auth/login.rs +++ b/crates/turborepo-auth/src/auth/login.rs @@ -3,7 +3,7 @@ use std::sync::Arc; pub use error::Error; use reqwest::Url; use tokio::sync::OnceCell; -use tracing::{debug, warn}; +use tracing::warn; use turborepo_api_client::{CacheClient, Client, TokenClient}; use turborepo_ui::{BOLD, ColorConfig, start_spinner}; @@ -50,7 +50,6 @@ pub async fn login( // Check if passed in token exists first. if !force { if let Some(token) = existing_token { - debug!("found existing turbo token"); let token = Token::existing(token.into()); if token .is_valid( @@ -66,7 +65,6 @@ pub async fn login( } else if login_url_configuration.contains("vercel.com") { match crate::auth::get_token_with_refresh().await { Ok(Some(token_str)) => { - debug!("found existing Vercel token (possibly refreshed)"); let token = Token::existing(token_str); if token .is_valid( @@ -81,12 +79,8 @@ pub async fn login( return Ok(token); } } - Ok(None) => { - debug!("no valid token found"); - } - Err(e) => { - debug!("error getting token with refresh: {}", e); - } + Ok(None) => {} + Err(_) => {} } } } diff --git a/crates/turborepo-auth/src/auth/mod.rs b/crates/turborepo-auth/src/auth/mod.rs index 620a3d7b1af7d..da7729914ba22 100644 --- a/crates/turborepo-auth/src/auth/mod.rs +++ b/crates/turborepo-auth/src/auth/mod.rs @@ -57,24 +57,6 @@ pub struct LogoutOptions { pub path: Option, } -fn extract_vercel_token() -> Result, Error> { - let vercel_config_dir = - turborepo_dirs::vercel_config_dir()?.ok_or_else(|| Error::ConfigDirNotFound)?; - - let vercel_token_path = - vercel_config_dir.join_components(&[VERCEL_TOKEN_DIR, VERCEL_TOKEN_FILE]); - let contents = std::fs::read_to_string(vercel_token_path)?; - - #[derive(serde::Deserialize)] - struct VercelToken { - // This isn't actually dead code, it's used by serde to deserialize the JSON. - #[allow(dead_code)] - token: Option, - } - - Ok(serde_json::from_str::(&contents)?.token) -} - /// Attempts to get a valid token with automatic refresh if expired. /// Falls back to turborepo/config.json if refresh fails. pub async fn get_token_with_refresh() -> Result, Error> { @@ -82,72 +64,33 @@ pub async fn get_token_with_refresh() -> Result, Error> { let vercel_config_dir = match turborepo_dirs::vercel_config_dir()? { Some(dir) => dir, - None => { - tracing::debug!("No Vercel config directory found"); - return Ok(None); - } + None => return Ok(None), }; let auth_path = vercel_config_dir.join_components(&[VERCEL_TOKEN_DIR, VERCEL_TOKEN_FILE]); - tracing::debug!("Reading auth tokens from: {}", auth_path); - // Try to read auth.json with token and refresh token let auth_tokens = Token::from_auth_file(&auth_path)?; - tracing::debug!( - "Auth tokens loaded - has token: {}, has refresh: {}, expires_at: {:?}", - auth_tokens.token.is_some(), - auth_tokens.refresh_token.is_some(), - auth_tokens.expires_at - ); - println!("hey hiiiii howdy"); - // If we have a token if let Some(token) = &auth_tokens.token { - // Check if token is expired if auth_tokens.is_expired() { - println!("hey hi howdy"); // Try to refresh the token if auth_tokens.refresh_token.is_some() { - match auth_tokens.refresh_token().await { - Ok(new_tokens) => { - tracing::info!("Successfully refreshed token"); - // Write the new tokens back to auth.json - if let Err(e) = new_tokens.write_to_auth_file(&auth_path) { - tracing::warn!("Failed to write refreshed token to auth.json: {}", e); - } else { - tracing::info!("Updated auth.json with new tokens"); - } - return Ok(new_tokens.token); - } - Err(e) => { - tracing::warn!("Failed to refresh token: {}", e); - // Fall through to try turborepo/config.json - } + if let Ok(new_tokens) = auth_tokens.refresh_token().await { + let _ = new_tokens.write_to_auth_file(&auth_path); + return Ok(new_tokens.token); } } - // Token expired and refresh failed, try turborepo/config.json - tracing::info!("Attempting to fall back to turborepo/config.json"); if let Ok(Some(config_dir)) = turborepo_dirs::config_dir() { let turbo_config_path = config_dir.join_components(&[TURBO_TOKEN_DIR, TURBO_TOKEN_FILE]); - tracing::debug!("Checking for fallback token at: {}", turbo_config_path); if let Ok(turbo_token) = Token::from_file(&turbo_config_path) { - tracing::info!("Found valid fallback token in turborepo/config.json"); return Ok(Some(turbo_token.into_inner().to_string())); - } else { - tracing::debug!("No valid token found in turborepo/config.json"); } - } else { - tracing::debug!("Could not locate config directory for fallback"); } - // No valid fallback token found - tracing::warn!("No valid token found after refresh attempt and fallback"); Ok(None) } else { - // Token is not expired, use it - tracing::debug!("Token is still valid, using existing token"); Ok(Some(token.clone())) } } else { diff --git a/crates/turborepo-auth/src/auth/sso.rs b/crates/turborepo-auth/src/auth/sso.rs index 063d7f6887dda..8978b361379f2 100644 --- a/crates/turborepo-auth/src/auth/sso.rs +++ b/crates/turborepo-auth/src/auth/sso.rs @@ -90,9 +90,7 @@ pub async fn sso_login( } } Ok(None) => {} - Err(e) => { - tracing::debug!("error getting token with refresh: {}", e); - } + Err(_) => {} } } } diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 70222552e7af4..5e1c9c4fe6d57 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -366,7 +366,6 @@ impl AuthTokens { pub fn is_expired(&self) -> bool { if let Some(expires_at) = self.expires_at { let current_time = current_unix_time_secs(); - println!("{}, {}", current_time, expires_at); current_time >= expires_at } else { false @@ -380,11 +379,6 @@ impl AuthTokens { .as_ref() .ok_or_else(|| Error::TokenNotFound)?; - tracing::debug!( - "Attempting to refresh token with refresh_token: {}", - refresh_token - ); - let client = reqwest::Client::new(); let params = [ ("refresh_token", refresh_token.as_str()), @@ -398,39 +392,20 @@ impl AuthTokens { .send() .await?; - tracing::debug!("{}", response.url()); - let status = response.status(); - tracing::debug!("Token refresh response status: {}", status); if !status.is_success() { - let body = response - .text() - .await - .unwrap_or_else(|_| "Unable to read response body".to_string()); - tracing::error!("Token refresh failed with status {}: {}", status, body); return Err(Error::FailedToGetToken); } - // Get the response text first for debugging let response_text = response.text().await?; - tracing::debug!("Token refresh response body: {}", response_text); - - let oauth_response: OAuthTokenResponse = - serde_json::from_str(&response_text).map_err(|e| { - tracing::error!( - "Failed to parse OAuth response: {}. Body was: {}", - e, - response_text - ); - e - })?; - tracing::info!("Token refresh successful"); + + let oauth_response: OAuthTokenResponse = serde_json::from_str(&response_text)?; Ok(AuthTokens { token: Some(oauth_response.access_token), refresh_token: Some(oauth_response.refresh_token), - expires_at: Some(current_unix_time_secs() + 8 * 60 * 60), // 8 hours from now + expires_at: Some(current_unix_time_secs() + 8 * 60 * 60), }) } From f312bef4854d33c8a5dc62fdbf809f17e83dcee7 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 1 Oct 2025 14:36:27 -0700 Subject: [PATCH 3/5] fmt --- crates/turborepo-auth/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 5e1c9c4fe6d57..bb770f2314fec 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -405,7 +405,7 @@ impl AuthTokens { Ok(AuthTokens { token: Some(oauth_response.access_token), refresh_token: Some(oauth_response.refresh_token), - expires_at: Some(current_unix_time_secs() + 8 * 60 * 60), + expires_at: Some(current_unix_time_secs() + 8 * 60 * 60), // 8 hours from now }) } From 45863c004c83024fff5a3b182f79a475f726a6b3 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 1 Oct 2025 14:41:12 -0700 Subject: [PATCH 4/5] fmt --- crates/turborepo-lib/src/commands/link.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/turborepo-lib/src/commands/link.rs b/crates/turborepo-lib/src/commands/link.rs index a7a3ac018854d..7cc8cc1bfb88d 100644 --- a/crates/turborepo-lib/src/commands/link.rs +++ b/crates/turborepo-lib/src/commands/link.rs @@ -19,7 +19,7 @@ use thiserror::Error; use turborepo_api_client::{CacheClient, Client}; #[cfg(not(test))] use turborepo_ui::CYAN; -use turborepo_ui::{BOLD, DialoguerTheme, GREY}; +use turborepo_ui::{DialoguerTheme, BOLD, GREY}; use turborepo_vercel_api::{CachingStatus, Team}; use crate::{ @@ -395,10 +395,10 @@ mod test { use turborepo_vercel_api_mock::start_test_server; use crate::{ - Args, - commands::{CommandBase, link}, + commands::{link, CommandBase}, config::TurborepoConfigBuilder, opts::Opts, + Args, }; #[tokio::test] From 6a204572a72995654859db0175b3ce4008a1cb89 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 1 Oct 2025 14:58:22 -0700 Subject: [PATCH 5/5] fmt --- crates/turborepo-auth/src/auth/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/turborepo-auth/src/auth/mod.rs b/crates/turborepo-auth/src/auth/mod.rs index da7729914ba22..3ed65bd98a428 100644 --- a/crates/turborepo-auth/src/auth/mod.rs +++ b/crates/turborepo-auth/src/auth/mod.rs @@ -74,11 +74,11 @@ pub async fn get_token_with_refresh() -> Result, Error> { if let Some(token) = &auth_tokens.token { if auth_tokens.is_expired() { // Try to refresh the token - if auth_tokens.refresh_token.is_some() { - if let Ok(new_tokens) = auth_tokens.refresh_token().await { - let _ = new_tokens.write_to_auth_file(&auth_path); - return Ok(new_tokens.token); - } + if auth_tokens.refresh_token.is_some() + && let Ok(new_tokens) = auth_tokens.refresh_token().await + { + let _ = new_tokens.write_to_auth_file(&auth_path); + return Ok(new_tokens.token); } if let Ok(Some(config_dir)) = turborepo_dirs::config_dir() {