Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ func newLogger(format OutputFormat, verbose bool) log.Logger {

// newCookieJar returns a new cookie jar instance.
func newCookieJar(machine machine.Machine) http.CookieJar {
cookiePath := filepath.Join(machine.HomeDirectory(), ConfigDirectoryName, CookieJarFileName)
// Remove stale zero-byte lock files. The juju/go4/lock library only does
// PID-based stale detection when the file has content; a 0-byte lock file
// (left by a process killed between O_TRUNC and PID write) blocks forever.
lockPath := cookiePath + ".lock"
if fi, err := os.Stat(lockPath); err == nil && fi.Size() == 0 {
os.Remove(lockPath)
}
return util.Must(cookiejar.New(&cookiejar.Options{
Filename: filepath.Join(machine.HomeDirectory(), ConfigDirectoryName, CookieJarFileName),
Filename: cookiePath,
}))
}

Expand Down
23 changes: 21 additions & 2 deletions pkg/appstore/appstore_bag.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,32 @@ func (t *appstore) Bag(input BagInput) (BagOutput, error) {
return BagOutput{}, fmt.Errorf("received unexpected status code: %d", res.StatusCode)
}

endpoint := res.Data.AuthEndpoint
if endpoint == "" {
endpoint = res.Data.URLBag.AuthEndpoint
}
// The new auth endpoint requires the /fast sub-path WITH a trailing slash.
// Without the trailing slash Apple returns 301 + an HTML redirect page
// (which the plist parser chokes on), and only the appstored UA gets a
// (download-grade) token. With the slash, the Configurator UA mints a
// commerce-grade token that buyProduct accepts.
if strings.Contains(endpoint, "auth.itunes.apple.com") {
if !strings.HasSuffix(endpoint, "/fast") && !strings.HasSuffix(endpoint, "/fast/") {
endpoint = endpoint + "/fast"
}
if !strings.HasSuffix(endpoint, "/") {
endpoint = endpoint + "/"
}
}

return BagOutput{
AuthEndpoint: res.Data.URLBag.AuthEndpoint,
AuthEndpoint: endpoint,
}, nil
}

type bagResult struct {
URLBag urlBag `plist:"urlBag,omitempty"`
URLBag urlBag `plist:"urlBag,omitempty"`
AuthEndpoint string `plist:"authenticateAccount,omitempty"`
}

type urlBag struct {
Expand Down
4 changes: 3 additions & 1 deletion pkg/appstore/appstore_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ func (t *appstore) parseLoginResponse(res *http.Result[loginResult], attempt int
err = ErrAuthCodeRequired
} else if res.Data.FailureType == "" && res.Data.CustomerMessage == CustomerMessageAccountDisabled {
err = NewErrorWithMetadata(errors.New("account is disabled"), res)
} else if res.Data.FailureType == "" && res.Data.CustomerMessage == CustomerMessageActionSignInPage {
err = NewErrorWithMetadata(errors.New("account requires browser sign-in (2FA or Apple ID review required)"), res)
} else if res.Data.FailureType != "" {
if res.Data.CustomerMessage != "" {
err = NewErrorWithMetadata(errors.New(res.Data.CustomerMessage), res)
Expand All @@ -160,7 +162,7 @@ func (t *appstore) parseLoginResponse(res *http.Result[loginResult], attempt int
func (t *appstore) loginRequest(email, password, authCode, guid, endpoint string, attempt int) http.Request {
return http.Request{
Method: http.MethodPOST,
URL: endpoint,
URL: util.IfEmpty(endpoint, fmt.Sprintf("https://%s%s", PrivateAuthDomain, PrivateAuthPathNative)),
ResponseFormat: http.ResponseFormatXML,
Headers: map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
Expand Down
4 changes: 4 additions & 0 deletions pkg/appstore/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
CustomerMessageAccountDisabled = "Your account is disabled."
CustomerMessageSubscriptionRequired = "Subscription Required"
CustomerMessagePasswordChanged = "Your password has changed."
CustomerMessageActionSignInPage = "AMD-Action::SP"

iTunesAPIDomain = "itunes.apple.com"
iTunesAPIPathSearch = "/search"
Expand All @@ -25,6 +26,9 @@ const (
PrivateAppStoreAPIPathPurchase = "/WebObjects/MZFinance.woa/wa/buyProduct"
PrivateAppStoreAPIPathDownload = "/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct"

PrivateAuthDomain = "auth." + iTunesAPIDomain
PrivateAuthPathNative = "/auth/v1/native/fast/"

HTTPHeaderStoreFront = "X-Set-Apple-Store-Front"
HTTPHeaderPod = "pod"

Expand Down
4 changes: 4 additions & 0 deletions pkg/http/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ const (
)

const (
// Configurator UA mints a commerce-grade password token (the "Aw..." token)
// that buyProduct accepts. The appstored UA only yields a download-grade
// token that buyProduct rejects with failureType 2034. Requires the auth
// endpoint to carry the trailing slash (see appstore_bag.go).
DefaultUserAgent = "Configurator/2.17 (Macintosh; OS X 15.2; 24C5089c) AppleWebKit/0620.1.16.11.6"
)