authentication.py•3.57 kB
"""
Apple Find My authentication flow.
"""
from pyicloud import PyiCloudService
from pyicloud.exceptions import PyiCloudAPIResponseException, PyiCloudFailedLoginException
from credentials import elicit_credentials, get_apple_id_from_secrets, get_apple_password_from_secrets, get_2fa_code, select_device, get_2sa_code
from fastmcp import Context
async def authenticate_client(ctx: Context, client: PyiCloudService) -> PyiCloudService:
"""Handle authentication flow for iCloud client."""
await ctx.info("🔐 Checking authentication status...")
# Check if already authenticated
if not client.requires_2fa and not client.requires_2sa and client.is_trusted_session:
await ctx.info("✅ Already authenticated")
return client
# Handle 2FA
if client.requires_2fa:
await ctx.info("🔐 2FA required")
await ctx.info("Check your Apple devices for verification code")
code = await get_2fa_code(ctx)
if not client.validate_2fa_code(code):
raise ValueError("❌ 2FA verification failed")
# Establish trusted session
if not client.is_trusted_session:
await ctx.info("🔒 Establishing trusted session...")
trust_result = client.trust_session()
if not trust_result:
await ctx.info("⚠️ Trusted session failed - may need 2FA again")
# Handle 2SA
elif client.requires_2sa:
await ctx.info("🔐 2SA required")
devices = client.trusted_devices
device_idx = await select_device(ctx, devices)
selected_device = devices[device_idx]
if not client.send_verification_code(selected_device):
raise ValueError("❌ Failed to send verification code")
await ctx.info(f"📱 Code sent to {selected_device.get('deviceName', 'device')}")
code = await get_2sa_code(ctx)
if not client.validate_verification_code(selected_device, code):
raise ValueError("❌ 2SA verification failed")
await ctx.info("✅ 2SA successful")
await ctx.info("✅ Authentication complete")
return client
async def create_authenticated_client(ctx: Context) -> PyiCloudService:
"""Create and authenticate iCloud client."""
# Try with stored credentials first
try:
apple_id = await get_apple_id_from_secrets()
password = await get_apple_password_from_secrets()
if apple_id and password:
await ctx.info(f"🔑 Found stored credentials for: {apple_id}")
client = PyiCloudService(apple_id, password)
return await authenticate_client(ctx, client)
elif apple_id and not password:
# Have Apple ID but no password - try without password (will use pyicloud keyring)
await ctx.info(f"🔑 Found Apple ID: {apple_id} (using pyicloud keyring for password)")
client = PyiCloudService(apple_id)
return await authenticate_client(ctx, client)
except PyiCloudFailedLoginException:
await ctx.info("⚠️ Stored credentials failed, will elicit new ones")
# Fall back to manual credentials
await ctx.info("🔐 Credentials required")
apple_id, password = await elicit_credentials(ctx)
try:
client = PyiCloudService(apple_id, password)
return await authenticate_client(ctx, client)
except PyiCloudFailedLoginException as e:
raise ValueError(f"❌ Authentication failed: {e}")
except PyiCloudAPIResponseException as e:
raise ValueError(f"❌ iCloud API error: {e}")