Skip to content

fix: trigger 2FA push notification on iOS 26.4+ (resolves the auth stall in mandarons/icloud-docker#426)#138

Merged
mandarons merged 7 commits into
mandarons:mainfrom
epheterson:fix/ios-26.4-auth
May 30, 2026
Merged

fix: trigger 2FA push notification on iOS 26.4+ (resolves the auth stall in mandarons/icloud-docker#426)#138
mandarons merged 7 commits into
mandarons:mainfrom
epheterson:fix/ios-26.4-auth

Conversation

@epheterson

Copy link
Copy Markdown
Contributor

Summary

Restores 2FA on iOS 26.4+ trusted devices.

Since iOS 26.4 (Feb 2026), validate_2fa_code() is unreachable in practice
because the 6-digit code never arrives on any trusted device — Apple changed
the flow to require an explicit PUT /verify/trusteddevice/securitycode
(no body) to initiate code delivery. Without it, callers see
"Please enter validation code" and wait forever.

This PR adds ICloudPyService.trigger_2fa_push_notification() — the explicit
PUT — and wires it into the bundled icloudpy CLI so it works out of the
box. The bundled cmdline.py calls it right after requires_2fa returns
True and before prompting the user for the code.

Other library consumers (mandarons/icloud-docker, etc.) need only add a
single api.trigger_2fa_push_notification() call before their own
prompt-for-code logic to inherit the fix.

Refs: mandarons/icloud-docker#426

Validation

  • 4 new unit tests in tests/test_auth.py:
    • test_trigger_2fa_push_notification_success (happy path via mock)
    • test_trigger_2fa_push_notification_includes_session_headers (scnt + session_id forwarding)
    • test_trigger_2fa_push_notification_api_failure_is_non_fatal (ICloudPyAPIResponseException → False)
    • test_trigger_2fa_push_notification_network_failure_is_non_fatal (ConnectionError / Timeout / SSLError → False)
  • Existing tests pass; only the pre-existing test_storage ordering issue
    remains, unrelated to this change.
  • Live-validated against a real Apple ID with an iOS 26.x trusted device:
    push notification arrived, code accepted, Photos + Drive services reachable.

Approach

Ported from icloud-photos-downloader/icloud_photos_downloader#1335. That fix
has been validated in the boredazfcuk/docker-icloudpd community since
2026-05-04 and is the known-working solution. This PR adapts the same
approach to icloudpy's smaller, simpler API surface.

Notes

  • Failure to trigger push is non-fatal (returns False — both ICloudPyAPIResponseException
    AND network-level exceptions like Timeout/ConnectionError/SSLError).
    A code may still arrive via SMS or another path, and callers can fall
    through to the validate_2fa_code call regardless.
  • The SMS-fallback piece of upstream PR #1335 (pyicloud_ipd/sms.py changes)
    is not ported here — icloudpy doesn't have an equivalent SMS parser. Its
    2SA flow uses validate_verification_code via /listDevices, which is a
    separate code path that hasn't been affected by the iOS 26.4 change.

epheterson and others added 5 commits May 27, 2026 09:25
Apple's auth flow changed around iOS 26 / early 2026. The 2FA prompt
no longer arrives on trusted devices unless the client first sends a
PUT to /verify/trusteddevice/securitycode (no body) to explicitly
request the push. Without this, requires_2fa returns True but the
user sees no code on any device — auth stalls.

Adds a new public method `ICloudPyService.trigger_2fa_push_notification()`
that does the PUT. Callers should invoke it before prompting the user for
the code. Failure is non-fatal (returns False) — a code may still arrive
via SMS or another path.

Wired into the bundled `icloudpy` CLI (cmdline.py) so it works out of
the box. Other callers (mandarons/icloud-docker, etc.) just need to
add a single `api.trigger_2fa_push_notification()` call before their
existing code prompt.

Ported from icloud_photos_downloader PR #1335 (which solved the same
issue in their fork of pyicloud). The fix has been validated by the
boredazfcuk/docker-icloudpd community since 2026-05-04 and is the
known working solution.

Tests:
- New trigger_2fa_push_notification test (HTTP 204 success path)
- New scnt + session_id headers test (forwarding correctness)
- New API exception non-fatal test (graceful failure)
- Existing 212 tests still pass (only pre-existing test_storage
  ordering issue remains, unrelated)
…re test

Addresses two code-review items on the iOS 26.4 fix:

1. trigger_2fa_push_notification() previously caught only ICloudPyAPIResponseException.
   Network/transport errors (ConnectionError, Timeout, SSLError) would propagate
   to callers despite the docstring promising non-fatal behavior. Broadened the
   catch to `except Exception` so all transient failures return False as advertised.
   The push is best-effort anyway — a code may still arrive via SMS or other paths.

2. Added test_trigger_2fa_push_notification_network_failure_is_non_fatal exercising
   three real requests exceptions (ConnectionError, Timeout, SSLError) to enforce
   the broadened contract.

Also dropped two duplicate `from unittest.mock import patch` re-imports inside
test methods — `patch` is already imported at module level.

213 passed + 1 pre-existing unrelated failure (test_storage).
CRITICAL-1 from pre-submission review. The earlier review-fix commit
(broaden exception catch) accidentally ADDED a second copy of the method
instead of REPLACING the original. Python silently took the second
(broader-catch) definition; the first was dead code that would produce a
SyntaxWarning in Python 3.12+ and would have been the first thing a
maintainer flagged on the upstream PR.

Removes the first (narrow-catch) definition. The remaining (broad-catch)
version is the one the tests already exercise and the one wired into
cmdline.py.
CI runs `ruff check && pytest`. The recent review-driven commits
introduced two trailing-comma issues:

- icloudpy/cmdline.py:241 — multi-line print() inside the iOS 26.4
  push-trigger fallback.
- tests/test_auth.py:568 — multi-line call in TestErrorCodeHandling.

Auto-fixed with `ruff check --fix`. No semantic change. Verified on a
python:3.10 docker that mirrors mandarons' CI: 214 passed, 80.60%
coverage (above the 78% gate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an explicit 2FA “push trigger” step required by Apple’s updated authentication flow (iOS 26.4+), exposing it as a new ICloudPyService API and invoking it in the bundled CLI before prompting for the 6‑digit code.

Changes:

  • Added ICloudPyService.trigger_2fa_push_notification() to initiate delivery of a 2FA code to trusted devices via PUT /verify/trusteddevice/securitycode.
  • Wired the new method into icloudpy’s CLI 2FA flow so users receive a prompt after the push is triggered.
  • Extended the test suite and mocks to cover the new flow and non-fatal error handling.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
icloudpy/base.py Adds the new trigger_2fa_push_notification() method to the core service API.
icloudpy/cmdline.py Calls the new trigger method when requires_2fa is true, before prompting for the code.
tests/__init__.py Updates the session mock to return a successful response for the new PUT endpoint.
tests/test_auth.py Adds unit tests validating the new method behavior (success, header forwarding, non-fatal failures).

Comment thread icloudpy/base.py
mandarons and others added 2 commits May 30, 2026 09:47
…e and return False

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@mandarons mandarons merged commit cdcb6cc into mandarons:main May 30, 2026
2 checks passed
@nicx

nicx commented Jun 2, 2026

Copy link
Copy Markdown

@mandarons tried the latest version with no success, I still get no popup on any trusted device. Any idea?

@epheterson

epheterson commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Looks like I hadn't wired this up right for re-auth only initial auth. I've fixed and tested this for re-auth and added a telegram flow through the existing integration.

This PR adds the icloudpy support and the following PR uses it: mandarons/icloud-docker#470

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants