44
55import  argparse 
66import  configparser 
7+ import  contextlib 
78import  logging 
89import  os 
910import  stat 
@@ -311,36 +312,60 @@ def fetch_zuliprc(zuliprc_path: str) -> None:
311312        print (in_color ("red" , "\n Incorrect Email(or Username) or Password!\n " ))
312313        login_data  =  get_api_key (realm_url )
313314
315+     zulip_key_path  =  os .path .join (
316+         os .path .dirname (os .path .abspath (zuliprc_path )), "zulip_key" 
317+     )
318+ 
314319    preferred_realm_url , login_id , api_key  =  login_data 
315320    save_zuliprc_failure  =  _write_zuliprc (
316-         zuliprc_path ,
317-         login_id = login_id ,
321+         to_path = zuliprc_path ,
322+         key_path = zulip_key_path ,
318323        api_key = api_key ,
324+         login_id = login_id ,
319325        server_url = preferred_realm_url ,
320326    )
321327    if  not  save_zuliprc_failure :
322-         print (f"Generated API key  saved at { zuliprc_path }  " )
328+         print (f"Generated config file  saved at { zuliprc_path }  " )
323329    else :
324330        exit_with_error (save_zuliprc_failure )
325331
326332
327333def  _write_zuliprc (
328-     to_path : str , * , login_id : str , api_key : str , server_url : str 
334+     to_path : str , * , key_path :  str ,  login_id : str , api_key : str , server_url : str 
329335) ->  str :
330336    """ 
331-     Writes a zuliprc file, returning a non-empty error string on failure 
332-     Only creates new private files; errors if file already exists 
337+     Writes both zuliprc and zulip_key files securely. 
338+     Ensures atomicity: if one file fails to write, cleans up the other. 
339+     Returns an empty string on success, or a descriptive error message on failure. 
333340    """ 
341+     zuliprc_created  =  False 
342+ 
334343    try :
344+         # Write zuliprc 
335345        with  open (
336346            os .open (to_path , os .O_CREAT  |  os .O_WRONLY  |  os .O_EXCL , 0o600 ), "w" 
337347        ) as  f :
338-             f .write (f"[api]\n email={ login_id } \n key={ api_key } \n site={ server_url }  " )
348+             f .write (
349+                 f"[api]\n email={ login_id } \n passcmd=cat zulip_key\n site={ server_url }  " 
350+             )
351+         zuliprc_created  =  True 
352+         # Write zulip_key 
353+         with  open (
354+             os .open (key_path , os .O_CREAT  |  os .O_WRONLY  |  os .O_EXCL , 0o600 ), "w" 
355+         ) as  f :
356+             f .write (api_key )
357+ 
339358        return  "" 
359+ 
340360    except  FileExistsError :
341-         return  f"zuliprc already exists at { to_path }  " 
361+         filename  =  to_path  if  not  zuliprc_created  else  key_path 
362+         return  f"FileExistsError: { filename }   already exists" 
342363    except  OSError  as  ex :
343-         return  f"{ ex .__class__ .__name__ }  : zuliprc could not be created at { to_path }  " 
364+         if  zuliprc_created :
365+             with  contextlib .suppress (Exception ):
366+                 os .remove (to_path )
367+         filename  =  key_path  if  zuliprc_created  else  to_path 
368+         return  f"{ ex .__class__ .__name__ }  : could not create { filename }   ({ ex }  )" 
344369
345370
346371def  parse_zuliprc (zuliprc_str : str ) ->  Dict [str , SettingData ]:
@@ -366,12 +391,12 @@ def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
366391            in_color (
367392                "red" ,
368393                "ERROR: Please ensure your zuliprc is NOT publicly accessible:\n " 
369-                 "  {0 }\n " 
370-                 "(it currently has permissions '{1 }')\n " 
394+                 f "  { zuliprc_path } \n "
395+                 f "(it currently has permissions '{ stat . filemode ( mode ) }  ')\n "
371396                "This can often be achieved with a command such as:\n " 
372-                 "  chmod og-rwx {0 }\n " 
397+                 f "  chmod og-rwx { zuliprc_path } \n "
373398                "Consider regenerating the [api] part of your zuliprc to ensure " 
374-                 "your account is secure." . format ( zuliprc_path ,  stat . filemode ( mode )) ,
399+                 "your account is secure." ,
375400            )
376401        )
377402        sys .exit (1 )
@@ -687,8 +712,8 @@ def print_setting(setting: str, data: SettingData, suffix: str = "") -> None:
687712            # Dump stats only after temporary file is closed (for Win NT+ case) 
688713            prof .dump_stats (profile_path )
689714            print (
690-                 "Profile data saved to {0 }.\n " 
691-                 "You can visualize it using e.g. `snakeviz {0 }`" . format ( profile_path ) 
715+                 f "Profile data saved to { profile_path }  .\n "
716+                 f "You can visualize it using e.g. `snakeviz { profile_path }  `"
692717            )
693718
694719        sys .exit (1 )
0 commit comments