diff --git a/src/commands/auth.rs b/src/commands/auth.rs index f713c4f..5e3a112 100644 --- a/src/commands/auth.rs +++ b/src/commands/auth.rs @@ -115,6 +115,21 @@ fn is_token_valid(token: &str) -> bool { exp > now + 60 // valid if expires more than 60s from now } +/// Build a Session from an OAuthSession (without touching token.json) +fn session_from_oauth(oauth: &oauth::OAuthSession) -> Session { + let pds_host = oauth.pds + .strip_prefix("https://") + .unwrap_or(&oauth.pds) + .trim_end_matches('/'); + Session { + did: oauth.did.clone(), + handle: oauth.handle.clone(), + access_jwt: oauth.access_token.clone(), + refresh_jwt: String::new(), // not used for OAuth + pds: Some(pds_host.to_string()), + } +} + /// Get OAuth session and return it if access_token is still valid. /// If expired, refresh it. If refresh fails, remove the OAuth session file /// so we cleanly fall back to legacy on next call. @@ -128,16 +143,11 @@ async fn try_oauth_session(is_bot: bool) -> Option { }; // If token is still valid, use it without refreshing if is_token_valid(&oauth_session.access_token) { - let session = if is_bot { - token::load_bot_session().ok() - } else { - token::load_session().ok() - }; - return session; + return Some(session_from_oauth(&oauth_session)); } // Token expired, try refresh match oauth::refresh_oauth_session(is_bot).await { - Ok((_oauth, session)) => Some(session), + Ok((new_oauth, _session)) => Some(session_from_oauth(&new_oauth)), Err(_) => { // Refresh failed — remove broken OAuth session so legacy works oauth::remove_oauth_session(is_bot); diff --git a/src/commands/oauth.rs b/src/commands/oauth.rs index 347f1ab..0f9b273 100644 --- a/src/commands/oauth.rs +++ b/src/commands/oauth.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::{self, Write}; -use super::token::{self, Session, BUNDLE_ID}; +use super::token::{Session, BUNDLE_ID}; fn load_site_url() -> Result { let config = super::token::load_config()?; @@ -537,6 +537,8 @@ pub async fn refresh_oauth_session(is_bot: bool) -> Result<(OAuthSession, Sessio .unwrap_or(&oauth.pds) .trim_end_matches('/'); + // Build compat session (for callers that need Session struct) + // Do NOT overwrite token.json — it holds legacy refresh_jwt let compat = Session { did: sub.to_string(), handle: oauth.handle.clone(), @@ -545,12 +547,6 @@ pub async fn refresh_oauth_session(is_bot: bool) -> Result<(OAuthSession, Sessio pds: Some(pds_host.to_string()), }; - if is_bot { - token::save_bot_session(&compat)?; - } else { - token::save_session(&compat)?; - } - return Ok((new_oauth, compat)); } @@ -782,31 +778,10 @@ pub async fn oauth_login(handle: &str, is_bot: bool) -> Result<()> { }; save_oauth_session(&oauth_session, is_bot)?; - // 10. Save compatible Session (for existing commands) - let pds_host = pds_url - .strip_prefix("https://") - .unwrap_or(&pds_url) - .trim_end_matches('/'); - - let compat_session = Session { - did: resolved_did.to_string(), - handle: handle.to_string(), - access_jwt: token_res.access_token, - refresh_jwt: token_res.refresh_token.unwrap_or_default(), - pds: Some(pds_host.to_string()), - }; - - if is_bot { - token::save_bot_session(&compat_session)?; - println!("Bot session saved."); - } else { - token::save_session(&compat_session)?; - println!("Session saved."); - } - + // 10. Report success (token.json is NOT overwritten — OAuth uses oauth_session.json only) println!( "Logged in as {} ({}) via OAuth", - compat_session.handle, compat_session.did + handle, resolved_did ); Ok(()) diff --git a/src/commands/pds.rs b/src/commands/pds.rs index 9dd0d2b..5a3a2a8 100644 --- a/src/commands/pds.rs +++ b/src/commands/pds.rs @@ -102,6 +102,28 @@ pub fn show_session(is_bot: bool) -> Result<()> { Ok(()) } +/// Refresh access token +pub async fn refresh(is_bot: bool) -> Result<()> { + use super::auth; + + let session = if is_bot { + auth::refresh_bot_session().await? + } else { + auth::refresh_session().await? + }; + + let has_oauth = super::oauth::has_oauth_session(is_bot); + let info = serde_json::json!({ + "did": session.did, + "handle": session.handle, + "pds": session.pds.as_deref().unwrap_or("bsky.social"), + "auth": if has_oauth { "oauth" } else { "legacy" }, + "refreshed": true, + }); + println!("{}", serde_json::to_string_pretty(&info)?); + Ok(()) +} + pub async fn check_versions(networks_path: &str) -> Result<()> { let networks = load_networks(networks_path)?; diff --git a/src/main.rs b/src/main.rs index d0e8f53..e6434af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -369,6 +369,13 @@ enum PdsCommands { #[arg(long)] bot: bool, }, + /// Refresh access token + #[command(alias = "r")] + Refresh { + /// Refresh bot token + #[arg(long)] + bot: bool, + }, } #[tokio::main] @@ -478,6 +485,9 @@ async fn main() -> Result<()> { PdsCommands::Session { bot } => { commands::pds::show_session(bot)?; } + PdsCommands::Refresh { bot } => { + commands::pds::refresh(bot).await?; + } } } Commands::Oauth { handle, bot } => {