Skip to content

Commit

Permalink
sms mfa using str instead of int to keep leading zeros (#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyNikiforov authored Nov 11, 2024
1 parent 1aec515 commit 8015de7
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 984 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- fix: sms MFA dropping leading zeros [#993](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/993)

## 1.24.3 (2024-11-03)

- fix: crashes when no imagetype sent by Apple [ref](https://github.com/boredazfcuk/docker-icloudpd/issues/680)
Expand Down
34 changes: 21 additions & 13 deletions src/icloudpd/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def request_2sa(icloud: PyiCloudService, logger: logging.Logger) -> None:
number = device["phoneNumber"]
alt_name = f"SMS to {number}"
name = device.get("deviceName", alt_name)
print(f" {i}: {name}")
click.echo(f" {i}: {name}")

device_index = click.prompt(
"Please choose an option:", default="0", type=click.IntRange(0, devices_count - 1)
Expand Down Expand Up @@ -138,7 +138,7 @@ def request_2fa(icloud: PyiCloudService, logger: logging.Logger) -> None:
sys.exit(1)

for i, device in enumerate(devices):
print(f" {device_index_alphabet[i]}: {device.obfuscated_number}")
click.echo(f" {device_index_alphabet[i]}: {device.obfuscated_number}")

index_str = f"..{device_index_alphabet[devices_count - 1]}" if devices_count > 1 else ""
index_or_code: str = ""
Expand All @@ -159,14 +159,14 @@ def request_2fa(icloud: PyiCloudService, logger: logging.Logger) -> None:
if index_or_code in device_index_alphabet:
if device_index_alphabet.index(index_or_code) > devices_count - 1:
click.echo(
f"Invalid index, should be {device_index_alphabet[0]}{index_str}. Try again"
f"Invalid index, should be ({device_index_alphabet[0]}{index_str}). Try again",
)
continue
else:
break
else:
click.echo(
f"Invalid index, should be {device_index_alphabet[0]}{index_str}. Try again"
f"Invalid index, should be ({device_index_alphabet[0]}{index_str}). Try again",
)
continue

Expand All @@ -178,7 +178,7 @@ def request_2fa(icloud: PyiCloudService, logger: logging.Logger) -> None:
continue

click.echo(
f"Should be index {device_index_alphabet[0]}{index_str} or six-digit code. Try again"
f"Should be index ({device_index_alphabet[0]}{index_str}) or six-digit code. Try again",
)

if index_or_code in device_index_alphabet:
Expand All @@ -188,10 +188,14 @@ def request_2fa(icloud: PyiCloudService, logger: logging.Logger) -> None:
if not icloud.send_2fa_code_sms(device.id):
logger.error("Failed to send two-factor authentication code")
sys.exit(1)
code: int = click.prompt(
"Please enter two-factor authentication code that you received over SMS",
type=click.IntRange(0, 999999),
)
while True:
code: str = click.prompt(
"Please enter two-factor authentication code that you received over SMS",
).strip()
if len(code) == 6 and code.isdigit():
break
click.echo("Invalid code, should be six digits. Try again")

if not icloud.validate_2fa_code_sms(device.id, code):
logger.error("Failed to verify two-factor authentication code")
sys.exit(1)
Expand All @@ -200,10 +204,14 @@ def request_2fa(icloud: PyiCloudService, logger: logging.Logger) -> None:
logger.error("Failed to verify two-factor authentication code")
sys.exit(1)
else:
code = click.prompt(
"Please enter two-factor authentication code", type=click.IntRange(0, 999999)
)
if not icloud.validate_2fa_code(str(code)):
while True:
code = click.prompt(
"Please enter two-factor authentication code",
).strip()
if len(code) == 6 and code.isdigit():
break
click.echo("Invalid code, should be six digits. Try again")
if not icloud.validate_2fa_code(code):
logger.error("Failed to verify two-factor authentication code")
sys.exit(1)
logger.info(
Expand Down
2 changes: 1 addition & 1 deletion src/pyicloud_ipd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ def validate_verification_code(self, device: Dict[str, Any], code: str) -> bool:

return not self.requires_2sa

def validate_2fa_code_sms(self, device_id: int, code:int) -> bool:
def validate_2fa_code_sms(self, device_id: int, code:str) -> bool:
"""Verifies a verification code received via Apple's 2FA system through SMS."""

oauth_session = self.get_oauth_session()
Expand Down
4 changes: 2 additions & 2 deletions src/pyicloud_ipd/sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,12 @@ def build_send_sms_code_request(context: _TrustedPhoneContextProvider, device_id
json = json)
return req

def build_verify_sms_code_request(context: _TrustedPhoneContextProvider, device_id: int, code: int) -> Request:
def build_verify_sms_code_request(context: _TrustedPhoneContextProvider, device_id: int, code: str) -> Request:
""" Builds a request for the list of trusted phone numbers for sms 2fa """

url = _auth_url(context.domain) + "/verify/phone/securitycode"

json = {"phoneNumber":{"id":device_id},"securityCode":{"code":str(code)},"mode":"sms"}
json = {"phoneNumber":{"id":device_id},"securityCode":{"code":code},"mode":"sms"}

req = _InternalRequest(
method="POST",
Expand Down
39 changes: 39 additions & 0 deletions tests/test_two_step_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,42 @@ def test_2fa_flow_valid_code(self) -> None:
self._caplog.text,
)
assert result.exit_code == 0

def test_2fa_flow_valid_code_zero_lead(self) -> None:
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
cookie_dir = os.path.join(base_dir, "cookie")

for dir in [base_dir, cookie_dir]:
recreate_path(dir)

with vcr.use_cassette(os.path.join(self.vcr_path, "2fa_flow_valid_code_zero_lead.yml")):
runner = CliRunner(env={"CLIENT_ID": "DE309E26-942E-11E8-92F5-14109FE0B321"})
result = runner.invoke(
main,
[
"--username",
"[email protected]",
"--password",
"password1",
"--no-progress-bar",
"--cookie-directory",
cookie_dir,
"--auth-only",
],
input="054321\n",
)
self.assertIn("DEBUG Authenticating...", self._caplog.text)
self.assertIn(
"INFO Two-factor authentication is required",
self._caplog.text,
)
self.assertIn(
"Please enter two-factor authentication code or device index (a) to send SMS with a code: 054321",
result.output,
)
self.assertIn(
"INFO Great, you're all set up. The script can now be run without "
"user interaction until 2FA expires.",
self._caplog.text,
)
assert result.exit_code == 0
Loading

0 comments on commit 8015de7

Please sign in to comment.