Skip to content
Open
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
9 changes: 8 additions & 1 deletion pkg/appstore/appstore_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

var (
ErrAuthCodeRequired = errors.New("auth code is required")
ErrInvalidAuthCode = errors.New("invalid or expired 2FA code")
)

type LoginInput struct {
Expand Down Expand Up @@ -133,6 +134,12 @@ func (t *appstore) parseLoginResponse(res *http.Result[loginResult], attempt int
}
} else if attempt == 1 && res.Data.FailureType == FailureTypeInvalidCredentials {
retry = true
} else if res.Data.FailureType == FailureTypeInvalidAuthCode {
err = ErrInvalidAuthCode
} else if res.Data.FailureType == FailureTypeInvalidCredentials && authCode != "" {
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

This logic handles the case where Apple's API returns FailureTypeInvalidCredentials ("-5000") when an auth code is provided. Consider adding a code comment explaining why "-5000" (typically indicating invalid username/password) is treated as an invalid auth code error when an auth code is present. This would help future maintainers understand that Apple's API can return "-5000" for both invalid credentials and invalid 2FA codes depending on context.

Suggested change
} else if res.Data.FailureType == FailureTypeInvalidCredentials && authCode != "" {
} else if res.Data.FailureType == FailureTypeInvalidCredentials && authCode != "" {
// Apple's API may return FailureTypeInvalidCredentials ("-5000") both for invalid
// username/password and invalid 2FA codes. When an auth code is present, treat this
// response as an invalid/expired 2FA code to reflect the actual error context.

Copilot uses AI. Check for mistakes.
err = ErrInvalidAuthCode
} else if res.Data.FailureType == "" && authCode != "" && res.Data.CustomerMessage == CustomerMessageBadLogin {
err = ErrInvalidAuthCode
} else if res.Data.FailureType == "" && authCode == "" && res.Data.CustomerMessage == CustomerMessageBadLogin {
err = ErrAuthCodeRequired
} else if res.Data.FailureType == "" && res.Data.CustomerMessage == CustomerMessageAccountDisabled {
Expand All @@ -141,7 +148,7 @@ func (t *appstore) parseLoginResponse(res *http.Result[loginResult], attempt int
if res.Data.CustomerMessage != "" {
err = NewErrorWithMetadata(errors.New(res.Data.CustomerMessage), res)
} else {
err = NewErrorWithMetadata(errors.New("something went wrong"), res)
err = NewErrorWithMetadata(fmt.Errorf("something went wrong (failureType: %s)", res.Data.FailureType), res)
}
} else if res.StatusCode != gohttp.StatusOK || res.Data.PasswordToken == "" || res.Data.DirectoryServicesID == "" {
err = NewErrorWithMetadata(errors.New("something went wrong"), res)
Expand Down
44 changes: 43 additions & 1 deletion pkg/appstore/appstore_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ var _ = Describe("AppStore (Login)", func() {
})
})

When("store API returns 5005 failure type (invalid auth code)", func() {
BeforeEach(func() {
mockClient.EXPECT().
Send(gomock.Any()).
Return(http.Result[loginResult]{
Data: loginResult{
FailureType: FailureTypeInvalidAuthCode,
},
}, nil)
})

It("returns ErrInvalidAuthCode error", func() {
_, err := as.Login(LoginInput{
Password: testPassword,
AuthCode: "123456",
})
Expect(err).To(Equal(ErrInvalidAuthCode))
})
})

Comment on lines +107 to +126
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

Missing test coverage for the scenario where FailureTypeInvalidCredentials is returned with an auth code provided (lines 139-140 of appstore_login.go). This test case would verify the behavior when Apple's API returns "-5000" with an auth code present.

Copilot uses AI. Check for mistakes.
When("store API returns error", func() {
BeforeEach(func() {
mockClient.EXPECT().
Expand All @@ -115,11 +135,12 @@ var _ = Describe("AppStore (Login)", func() {
}, nil)
})

It("returns error", func() {
It("returns error with failure type", func() {
_, err := as.Login(LoginInput{
Password: testPassword,
})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("something went wrong (failureType: random-error)"))
})
})

Expand Down Expand Up @@ -163,6 +184,27 @@ var _ = Describe("AppStore (Login)", func() {
})
})

When("store API returns invalid auth code with auth code provided", func() {
BeforeEach(func() {
mockClient.EXPECT().
Send(gomock.Any()).
Return(http.Result[loginResult]{
Data: loginResult{
FailureType: "",
CustomerMessage: CustomerMessageBadLogin,
},
}, nil)
})

It("returns ErrInvalidAuthCode error", func() {
_, err := as.Login(LoginInput{
Password: testPassword,
AuthCode: "wrongcode",
})
Expect(err).To(Equal(ErrInvalidAuthCode))
})
})

When("store API redirects", func() {
const (
testRedirectLocation = "https://" + PrivateAppStoreAPIDomain + PrivateAppStoreAPIPathAuthenticate + "?PRH=31&Pod=31"
Expand Down
1 change: 1 addition & 0 deletions pkg/appstore/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const (
FailureTypePasswordTokenExpired = "2034"
FailureTypeLicenseNotFound = "9610"
FailureTypeTemporarilyUnavailable = "2059"
FailureTypeInvalidAuthCode = "5005"

CustomerMessageBadLogin = "MZFinance.BadLogin.Configurator_message"
CustomerMessageAccountDisabled = "Your account is disabled."
Expand Down
Loading