@@ -173,7 +173,12 @@ async fn handle_login(args: &[String]) -> Result<(), GwsError> {
173173
174174 if token. token ( ) . is_some ( ) {
175175 // Read yup-oauth2's token cache to extract the refresh_token.
176- let token_data = std:: fs:: read_to_string ( & temp_path) . unwrap_or_default ( ) ;
176+ // EncryptedTokenStorage stores data encrypted, so we must decrypt first.
177+ let token_data = std:: fs:: read ( & temp_path)
178+ . ok ( )
179+ . and_then ( |bytes| crate :: credential_store:: decrypt ( & bytes) . ok ( ) )
180+ . and_then ( |decrypted| String :: from_utf8 ( decrypted) . ok ( ) )
181+ . unwrap_or_default ( ) ;
177182 let refresh_token = extract_refresh_token ( & token_data) . ok_or_else ( || {
178183 GwsError :: Auth (
179184 "OAuth flow completed but no refresh token was returned. \
@@ -891,16 +896,39 @@ fn handle_logout() -> Result<(), GwsError> {
891896}
892897
893898/// Extract refresh_token from yup-oauth2 v12 token cache.
894- /// Format: [{"scopes":[...], "token":{"access_token":..., "refresh_token":...}}]
899+ ///
900+ /// Supports two formats:
901+ /// 1. Array format (yup-oauth2 default file storage):
902+ /// [{"scopes":[...], "token":{"access_token":..., "refresh_token":...}}]
903+ /// 2. Object/HashMap format (EncryptedTokenStorage serialization):
904+ /// {"scope_key": {"access_token":..., "refresh_token":..., ...}}
895905pub fn extract_refresh_token ( token_data : & str ) -> Option < String > {
896906 let cache: serde_json:: Value = serde_json:: from_str ( token_data) . ok ( ) ?;
897- cache. as_array ( ) ?. iter ( ) . find_map ( |entry| {
898- entry
899- . get ( "token" )
900- . and_then ( |t| t. get ( "refresh_token" ) )
901- . and_then ( |v| v. as_str ( ) )
902- . map ( |s| s. to_string ( ) )
903- } )
907+
908+ // Format 1: array of {scopes, token} entries
909+ if let Some ( arr) = cache. as_array ( ) {
910+ let result = arr. iter ( ) . find_map ( |entry| {
911+ entry
912+ . get ( "token" )
913+ . and_then ( |t| t. get ( "refresh_token" ) )
914+ . and_then ( |v| v. as_str ( ) )
915+ . map ( |s| s. to_string ( ) )
916+ } ) ;
917+ if result. is_some ( ) {
918+ return result;
919+ }
920+ }
921+
922+ // Format 2: HashMap<String, TokenInfo> — values are TokenInfo structs
923+ if let Some ( obj) = cache. as_object ( ) {
924+ for value in obj. values ( ) {
925+ if let Some ( rt) = value. get ( "refresh_token" ) . and_then ( |v| v. as_str ( ) ) {
926+ return Some ( rt. to_string ( ) ) ;
927+ }
928+ }
929+ }
930+
931+ None
904932}
905933
906934/// Parse --scopes or --readonly from args, falling back to DEFAULT_SCOPES.
@@ -1223,8 +1251,8 @@ mod tests {
12231251
12241252 #[ test]
12251253 fn extract_refresh_token_object_format ( ) {
1226- // Should return None — we only support array format
1254+ // HashMap<String, TokenInfo> format from EncryptedTokenStorage
12271255 let data = r#"{"key":{"access_token":"ya29","refresh_token":"1//tok"}}"# ;
1228- assert_eq ! ( extract_refresh_token( data) , None ) ;
1256+ assert_eq ! ( extract_refresh_token( data) , Some ( "1//tok" . to_string ( ) ) ) ;
12291257 }
12301258}
0 commit comments