@@ -4,9 +4,11 @@ import (
4
4
"encoding/json"
5
5
"errors"
6
6
"fmt"
7
+ "strconv"
7
8
"strings"
8
9
9
10
"github.com/majd/ipatool/v2/pkg/http"
11
+ "github.com/majd/ipatool/v2/pkg/util"
10
12
)
11
13
12
14
var (
@@ -31,7 +33,7 @@ func (t *appstore) Login(input LoginInput) (LoginOutput, error) {
31
33
32
34
guid := strings .ReplaceAll (strings .ToUpper (macAddr ), ":" , "" )
33
35
34
- acc , err := t .login (input .Email , input .Password , input .AuthCode , guid , 0 )
36
+ acc , err := t .login (input .Email , input .Password , input .AuthCode , guid )
35
37
if err != nil {
36
38
return LoginOutput {}, err
37
39
}
@@ -59,28 +61,36 @@ type loginResult struct {
59
61
PasswordToken string `plist:"passwordToken,omitempty"`
60
62
}
61
63
62
- func (t * appstore ) login (email , password , authCode , guid string , attempt int ) (Account , error ) {
63
- request := t .loginRequest (email , password , authCode , guid )
64
- res , err := t .loginClient .Send (request )
65
-
66
- if err != nil {
67
- return Account {}, fmt .Errorf ("request failed: %w" , err )
68
- }
69
-
70
- if attempt == 0 && res .Data .FailureType == FailureTypeInvalidCredentials {
71
- return t .login (email , password , authCode , guid , 1 )
72
- }
73
-
74
- if res .Data .FailureType != "" && res .Data .CustomerMessage != "" {
75
- return Account {}, NewErrorWithMetadata (errors .New (res .Data .CustomerMessage ), res )
64
+ func (t * appstore ) login (email , password , authCode , guid string ) (Account , error ) {
65
+ redirect := ""
66
+ var err error
67
+ retry := true
68
+ var res http.Result [loginResult ]
69
+
70
+ for attempt := 1 ; retry && attempt <= 4 ; attempt ++ {
71
+ ac := authCode
72
+ if attempt == 1 {
73
+ ac = ""
74
+ }
75
+ request := t .loginRequest (email , password , ac , guid , attempt )
76
+ request .URL , redirect = util .IfEmpty (redirect , request .URL ), ""
77
+ res , err = t .loginClient .Send (request )
78
+ if err != nil {
79
+ return Account {}, fmt .Errorf ("request failed: %w" , err )
80
+ }
81
+
82
+ if retry , redirect , err = t .parseLoginResponse (& res , attempt , authCode ); err != nil {
83
+ return Account {}, err
84
+ }
76
85
}
77
86
78
- if res . Data . FailureType != "" {
79
- return Account {}, NewErrorWithMetadata (errors .New ("something went wrong " ), res )
87
+ if retry {
88
+ return Account {}, NewErrorWithMetadata (errors .New ("too many attempts " ), res )
80
89
}
81
90
82
- if res .Data .FailureType == "" && authCode == "" && res .Data .CustomerMessage == CustomerMessageBadLogin {
83
- return Account {}, ErrAuthCodeRequired
91
+ sf , err := res .GetHeader (HTTPHeaderStoreFront )
92
+ if err != nil {
93
+ return Account {}, NewErrorWithMetadata (fmt .Errorf ("failed to get storefront header: %w" , err ), res )
84
94
}
85
95
86
96
addr := res .Data .Account .Address
@@ -89,7 +99,7 @@ func (t *appstore) login(email, password, authCode, guid string, attempt int) (A
89
99
Email : res .Data .Account .Email ,
90
100
PasswordToken : res .Data .PasswordToken ,
91
101
DirectoryServicesID : res .Data .DirectoryServicesID ,
92
- StoreFront : res . Headers [ HTTPHeaderStoreFront ] ,
102
+ StoreFront : sf ,
93
103
Password : password ,
94
104
}
95
105
@@ -106,39 +116,46 @@ func (t *appstore) login(email, password, authCode, guid string, attempt int) (A
106
116
return acc , nil
107
117
}
108
118
109
- func (t * appstore ) loginRequest (email , password , authCode , guid string ) http.Request {
110
- attempt := "4"
111
- if authCode != "" {
112
- attempt = "2"
119
+ func (t * appstore ) parseLoginResponse (res * http.Result [loginResult ], attempt int , authCode string ) (retry bool , redirect string , err error ) {
120
+ if res .StatusCode == 302 {
121
+ if redirect , err = res .GetHeader ("location" ); err != nil {
122
+ err = fmt .Errorf ("failed to retrieve redirect location: %w" , err )
123
+ } else {
124
+ retry = true
125
+ }
126
+ } else if attempt == 1 && res .Data .FailureType == FailureTypeInvalidCredentials {
127
+ retry = true
128
+ } else if res .Data .FailureType == "" && authCode == "" && res .Data .CustomerMessage == CustomerMessageBadLogin {
129
+ err = ErrAuthCodeRequired
130
+ } else if res .Data .FailureType != "" {
131
+ if res .Data .CustomerMessage != "" {
132
+ err = NewErrorWithMetadata (errors .New (res .Data .CustomerMessage ), res )
133
+ } else {
134
+ err = NewErrorWithMetadata (errors .New ("something went wrong" ), res )
135
+ }
136
+ } else if res .StatusCode != 200 || res .Data .PasswordToken == "" || res .Data .DirectoryServicesID == "" {
137
+ err = NewErrorWithMetadata (errors .New ("something went wrong" ), res )
113
138
}
139
+ return
140
+ }
114
141
142
+ func (t * appstore ) loginRequest (email , password , authCode , guid string , attempt int ) http.Request {
115
143
return http.Request {
116
144
Method : http .MethodPOST ,
117
- URL : t . authDomain ( authCode , guid ),
145
+ URL : fmt . Sprintf ( "https://%s%s" , PrivateAppStoreAPIDomain , PrivateAppStoreAPIPathAuthenticate ),
118
146
ResponseFormat : http .ResponseFormatXML ,
119
147
Headers : map [string ]string {
120
148
"Content-Type" : "application/x-www-form-urlencoded" ,
121
149
},
122
150
Payload : & http.XMLPayload {
123
151
Content : map [string ]interface {}{
124
- "appleId" : email ,
125
- "attempt" : attempt ,
126
- "createSession" : "true" ,
127
- "guid" : guid ,
128
- "password" : fmt .Sprintf ("%s%s" , password , authCode ),
129
- "rmp" : "0" ,
130
- "why" : "signIn" ,
152
+ "appleId" : email ,
153
+ "attempt" : strconv .Itoa (attempt ),
154
+ "guid" : guid ,
155
+ "password" : fmt .Sprintf ("%s%s" , password , authCode ),
156
+ "rmp" : "0" ,
157
+ "why" : "signIn" ,
131
158
},
132
159
},
133
160
}
134
161
}
135
-
136
- func (* appstore ) authDomain (authCode , guid string ) string {
137
- prefix := PrivateAppStoreAPIDomainPrefixWithoutAuthCode
138
- if authCode != "" {
139
- prefix = PrivateAppStoreAPIDomainPrefixWithAuthCode
140
- }
141
-
142
- return fmt .Sprintf (
143
- "https://%s-%s%s?guid=%s" , prefix , PrivateAppStoreAPIDomain , PrivateAppStoreAPIPathAuthenticate , guid )
144
- }
0 commit comments