Skip to content

fix #464: fall back to redownload endpoint on FailureType 5002#477

Open
koraytutuncu wants to merge 9 commits into
majd:mainfrom
koraytutuncu:fix/issue-464-redownload-fallback
Open

fix #464: fall back to redownload endpoint on FailureType 5002#477
koraytutuncu wants to merge 9 commits into
majd:mainfrom
koraytutuncu:fix/issue-464-redownload-fallback

Conversation

@koraytutuncu
Copy link
Copy Markdown
Contributor

@koraytutuncu koraytutuncu commented May 9, 2026

Fixes #464.

Problem

Apple's volumeStoreDownloadProduct endpoint returns FailureType 5002 for consumer Apple IDs hitting apps with an existing license — Microsoft Teams, Office, Facebook, Spotify, and others — breaking download, list-versions, and get-version-metadata. That endpoint is Apple's VPP/ABM path; it just happened to accept some apps as a coincidence.

Approach

Per @majd's feedback on the previous version of this PR:

We should resolve this from the bag rather than hardcoding it here.

Apple's bag at init.itunes.apple.com/bag.xml already advertises a redownloadProduct key alongside the existing authenticateAccount. The URL is now resolved at runtime from the bag, not hardcoded anywhere.

Changes

  • BagOutput.DownloadEndpointurlBag parses the redownloadProduct key from the bag. Pure additive in its own commit; no caller consumes it yet.
  • DownloadInput.Endpoint / ListVersionsInput.Endpoint / GetVersionMetadataInput.Endpoint — each cmd/* fetches the bag at the top of its retry function and threads the URL through, mirroring how LoginInput.Endpoint already works for authenticateAccount. The bag fetch that previously lived inside the token-expired retry branch is just promoted, so the same fetch serves both auth and download.
  • appExtVrsId payload key — what the redownload endpoint expects for version pinning, replacing externalVersionId.
  • PrivateAppStoreAPIPathDownload removed — no remaining references after the three call sites move to bag.DownloadEndpoint. Dead constants are slop.
  • 5002 → ErrPasswordTokenExpired mapping in Download removed — that mapping only existed to paper over the wrong endpoint; with the right endpoint, 5002 doesn't appear.

Branch history

Rather than force-pushing the branch, the prior two commits (47c7040, 3aa4a86) are kept reachable and explicitly reverted, so your earlier review comments stay anchored and accessible. The revert restores the tree to main; the six new commits build the bag-resolved approach on top. The branch can be merged as-is; the revert + new commits net out to the clean final diff.

Each commit builds and passes go test ./... independently.

Verification

Against a real consumer Apple ID:

  • download com.microsoft.skype.teams → 350 MB valid IPA, Payload/TeamSpaceApp.app/ with code signature, plug-in extensions, and TeamSpaceApp.sinf intact.
  • list-versions com.microsoft.skype.teams → full external version identifier list.
  • go test ./... → green.

Same single-environment caveat as the original disclosure: Apple's responses on these endpoints aren't documented and vary by account state, storefront, family-sharing posture, and device-context fields. Other reporters confirming this fixes their 5002s on different apps would help validate generality.

Failure mode worth flagging

There is no fallback to volumeStoreDownloadProduct. If Apple removes the redownloadProduct key from the bag, bag.DownloadEndpoint will be empty and the request will fail with a generic HTTP error. This is the same trust the existing code already places in the bag for authenticateAccount — keeping the old hardcoded URL as a fallback would reintroduce the exact 5002 bug this PR fixes. An explicit empty-string check at the call sites (clearer error message: "bag does not advertise a download endpoint") is a 3-line follow-up if you want it.

Sources

  • #464
  • Previous review feedback in this PR

Disclosure: code written by Claude Code; verified end-to-end against a live test account in one environment.


Summary by cubic

Route App Store requests through the bag-resolved redownload endpoint to avoid FailureType 5002, restoring download, list-versions, and get-version-metadata for affected apps (fixes #464). Also fixes the interactive flag lookup so auth login and download interactive mode work again.

  • Bug Fixes
    • Resolve redownloadProduct URL from the bag and pass it to Download, ListVersions, and GetVersionMetadata; use appExtVrsId for version pinning (pkg/appstore and cmd/*).
    • Remove 5002ErrPasswordTokenExpired mapping in Download to stop re-login loops.
    • When Items is empty, show customerMessage instead of “invalid response”.
    • Use typed interactiveKey in cmd/auth.go and cmd/download.go to prevent a panic and restore the progress bar.

Written for commit 0321942. Summary will update on new commits.

koraytutuncu and others added 2 commits May 9, 2026 15:56
PR majd#466 introduced a typed contextKey (interactiveKey) for the interactive
flag but auth.go and download.go still queried the context with the raw
string "interactive", which never matches a value stored under a
contextKey-typed key. As a result, auth login panicked on a nil-to-bool
type assertion the first time it ran post-majd#466, and download silently
treated the session as non-interactive (no progress bar).

Switch both call sites to look up via interactiveKey and use the
comma-ok form of the type assertion to avoid the panic.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…jd#464)

Apple's volumeStoreDownloadProduct path on buy.itunes.apple.com (the
endpoint ipatool drives for download, list-versions, and
get-version-metadata) now responds with FailureType 5002 and Items=null
for a number of apps the calling account already owns -- Microsoft
Teams, the Office bundle, Spotify, Facebook, etc. The bag advertises a
separate redownload endpoint at
https://downloaddispatch.itunes.apple.com/r/redownload that accepts the
same plist payload (with appExtVrsId in place of externalVersionId for
version pinning) and returns the full download metadata for those apps.

When the primary request comes back with FailureType 5002, retry the
same call against the redownload endpoint and use that response. If the
fallback also fails, surface its customerMessage in the empty-Items
branch so users see a meaningful error (e.g. "Redownload Unavailable
with This Apple Account") instead of "invalid response."

Also stop mapping FailureType 5002 to ErrPasswordTokenExpired in the
download flow. The mapping was added in majd#468 on the assumption that the
condition was a stale token, but re-authenticating doesn't change
Apple's response for the affected apps; with the redownload fallback in
place the false re-login retry only obscures the real outcome.

Verified end-to-end: list-versions and get-version-metadata return the
full version history for com.microsoft.skype.teams, and download
produces a valid 366 MB .ipa with the Microsoft Teams app bundle
intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Comment thread pkg/appstore/constants.go Outdated
Comment on lines +28 to +29
PrivateDownloadDispatchAPIDomain = "downloaddispatch." + iTunesAPIDomain
PrivateDownloadDispatchAPIPath = "/r/redownload"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

We should resolve this from the bag rather than hardcoding it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I’ll try to look into this more when I have some time.

koraytutuncu and others added 7 commits May 11, 2026 13:14
Reverts both commits that previously sat on this branch:

- 3aa4a86 "fix: fall back to redownload endpoint when MZFinance returns 5002 (majd#464)"
- 47c7040 "fix: look up interactive context value with the typed key"

After this commit the tree is identical to majd/ipatool main (85ae82d),
clearing the way for the bag-resolved approach to land as a clean series
of focused commits without force-pushing the branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
cmd/root.go writes the interactive flag using a typed contextKey, but
cmd/auth.go and cmd/download.go read it back with the raw string
"interactive". The lookup misses, leaving a nil interface that crashes
the bare type assertion in auth.go on every `auth login` invocation
and silently disables the progress bar in `download` interactive mode.

Switch both call sites to look the value up with the typed key and the
comma-ok variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Apple's bag at init.itunes.apple.com/bag.xml advertises a redownloadProduct
key alongside the existing authenticateAccount. Parse it and surface it
via BagOutput.DownloadEndpoint so callers can resolve the consumer
redownload URL at runtime instead of hardcoding it.

This commit only exposes the value; no caller consumes it yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The volumeStoreDownloadProduct endpoint is Apple's VPP/ABM path. Consumer
Apple IDs hit FailureType 5002 ("license already exists") on it for apps
the account already owns - Microsoft Teams, Office, Facebook, Spotify
and others - breaking ipatool's download for those apps.

Apple's bag advertises a separate redownloadProduct endpoint at
downloaddispatch.itunes.apple.com/r/redownload for exactly this case.
Switch Download to consume the URL via DownloadInput.Endpoint, threaded
from cmd/download.go's bag fetch which is now promoted out of the
token-expired retry branch to the top of the retry function.

Drop the 5002 -> ErrPasswordTokenExpired workaround that only existed
because of the wrong endpoint. Switch the version-pinning payload key
from externalVersionId to appExtVrsId which is what the redownload
endpoint expects.

Fixes the download half of majd#464.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ListVersions used the same volumeStoreDownloadProduct path and hit the
same 5002 wall as Download for the affected apps. Switch it to the
bag-resolved redownload URL by adding ListVersionsInput.Endpoint and
threading it from cmd/list_versions.go's bag fetch, promoted out of the
token-expired branch the same way the download command was.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
GetVersionMetadata hit the same 5002 issue on volumeStoreDownloadProduct.
Same fix as the other two: GetVersionMetadataInput.Endpoint threaded
from cmd/get_version_metadata.go's bag fetch.

Also switch the version-pinning payload key from externalVersionId to
appExtVrsId, which is what the redownload endpoint expects.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
After Download, ListVersions and GetVersionMetadata moved to bag-resolved
URLs, PrivateAppStoreAPIPathDownload has no remaining references in the
package and would be dead code.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
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.

Cannot list versions/download certain apps (Microsoft Teams)

2 participants