feat: implement graceful fallback to config.json for OTP secret storage when the keyring module is unavailable.

This commit is contained in:
CPTN Cosmo 2026-02-02 02:27:56 +01:00
parent d52743d69c
commit 252a7ed239
No known key found for this signature in database

View file

@ -13,7 +13,11 @@ import hashlib
import os
import shutil
import configparser
import keyring
try:
import keyring
HAS_KEYRING = True
except ImportError:
HAS_KEYRING = False
import getpass
import urllib.parse
@ -172,42 +176,51 @@ def detect_launch_command():
def get_secret(config, config_path):
"""
Retrieves the OTP secret in the following priority:
1. System Keyring (SERVICE_NAME, USERNAME)
2. config.json (Legacy) - if found, migrates to Keyring and removes from config.
3. User Input - prompts user and saves to Keyring.
1. System Keyring (SERVICE_NAME, USERNAME) - if available
2. config.json - if Keyring unavailable or secret found there (and Keyring unavailable for migration)
3. User Input - prompts user and saves to Keyring (if available) or config.json.
"""
SERVICE_NAME = "XIVLauncherWrapper"
USERNAME = "OTP_SECRET"
# 1. Try Keyring
try:
secret = keyring.get_password(SERVICE_NAME, USERNAME)
if secret:
return secret
except Exception as e:
print(f"Warning: Failed to access keyring: {e}")
if HAS_KEYRING:
try:
secret = keyring.get_password(SERVICE_NAME, USERNAME)
if secret:
return secret
except Exception as e:
print(f"Warning: Failed to access keyring: {e}")
# 2. Try Config (Legacy/Migration)
# 2. Try Config
if config and "secret" in config:
secret = config["secret"]
if secret and secret != "YOUR_BASE32_SECRET_HERE":
print("Migrating secret from config.json to system keyring...")
try:
keyring.set_password(SERVICE_NAME, USERNAME, secret)
# Remove from config
del config["secret"]
with open(config_path, 'w') as f:
json.dump(config, f, indent=4)
print("Secret migrated successfully and removed from config.json.")
return secret
except Exception as e:
print(f"Error migrating to keyring: {e}")
print("Continuing with secret from config...")
return secret
if HAS_KEYRING:
print("Migrating secret from config.json to system keyring...")
try:
keyring.set_password(SERVICE_NAME, USERNAME, secret)
# Remove from config
del config["secret"]
with open(config_path, 'w') as f:
json.dump(config, f, indent=4)
print("Secret migrated successfully and removed from config.json.")
return secret
except Exception as e:
print(f"Error migrating to keyring: {e}")
print("Continuing with secret from config...")
return secret
else:
# No keyring, just use config
return secret
# 3. Prompt User
print("\nOTP Secret not found in keyring.")
if HAS_KEYRING:
print("\nOTP Secret not found in keyring.")
else:
print("\nOTP Secret not found in config.json and keyring module is missing.")
print("Please enter your TOTP Secret (base32) or otpauth:// URL.")
while True:
try:
@ -222,16 +235,26 @@ def get_secret(config, config_path):
print("Invalid secret format. Please try again.")
continue
try:
keyring.set_password(SERVICE_NAME, USERNAME, parsed)
print("Secret saved to system keyring.")
return parsed
except Exception as e:
print(f"Error saving to keyring: {e}")
# If we can't save to keyring, we might just return it in memory for this session
# But typically we want persistence.
print("Proceeding with in-memory secret for this session.")
return parsed
if HAS_KEYRING:
try:
keyring.set_password(SERVICE_NAME, USERNAME, parsed)
print("Secret saved to system keyring.")
return parsed
except Exception as e:
print(f"Error saving to keyring: {e}")
print("Proceeding with in-memory secret for this session.")
return parsed
else:
# Save to config.json
try:
config['secret'] = parsed
with open(config_path, 'w') as f:
json.dump(config, f, indent=4)
print(f"Secret saved to {config_path}")
return parsed
except Exception as e:
print(f"Error saving to config.json: {e}")
return parsed
except KeyboardInterrupt:
print("\nOperation cancelled.")