1
+ import { isInteractive } from "../../common/helpers" ;
2
+
1
3
export class ApplePortalSessionService implements IApplePortalSessionService {
2
4
private loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com" ;
3
5
private defaultLoginConfig = {
@@ -7,42 +9,33 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
7
9
8
10
constructor (
9
11
private $applePortalCookieService : IApplePortalCookieService ,
12
+ private $errors : IErrors ,
10
13
private $httpClient : Server . IHttpClient ,
11
- private $logger : ILogger
14
+ private $logger : ILogger ,
15
+ private $prompter : IPrompter
12
16
) { }
13
17
14
- public async createUserSession ( credentials : ICredentials ) : Promise < IApplePortalUserDetail > {
15
- const loginConfig = await this . getLoginConfig ( ) ;
16
- const loginUrl = `${ loginConfig . authServiceUrl } /auth/signin` ;
17
- const loginResponse = await this . $httpClient . httpRequest ( {
18
- url : loginUrl ,
19
- method : "POST" ,
20
- body : JSON . stringify ( {
21
- accountName : credentials . username ,
22
- password : credentials . password ,
23
- rememberMe : true
24
- } ) ,
25
- headers : {
26
- 'Content-Type' : 'application/json' ,
27
- 'X-Requested-With' : 'XMLHttpRequest' ,
28
- 'X-Apple-Widget-Key' : loginConfig . authServiceKey ,
29
- 'Accept' : 'application/json, text/javascript'
30
- }
31
- } ) ;
32
-
33
- this . $applePortalCookieService . updateUserSessionCookie ( loginResponse . headers [ "set-cookie" ] ) ;
18
+ public async createUserSession ( credentials : ICredentials , opts ?: IAppleCreateUserSessionOptions ) : Promise < IApplePortalUserDetail > {
19
+ const loginResult = await this . login ( credentials , opts ) ;
34
20
35
- const sessionResponse = await this . $httpClient . httpRequest ( {
36
- url : "https://appstoreconnect.apple.com/olympus/v1/session" ,
37
- method : "GET" ,
38
- headers : {
39
- 'Cookie' : this . $applePortalCookieService . getUserSessionCookie ( )
21
+ if ( ! opts || ! opts . sessionBase64 ) {
22
+ if ( loginResult . isTwoFactorAuthenticationEnabled ) {
23
+ const authServiceKey = ( await this . getLoginConfig ( ) ) . authServiceKey ;
24
+ await this . handleTwoFactorAuthentication ( loginResult . scnt , loginResult . xAppleIdSessionId , authServiceKey ) ;
40
25
}
41
- } ) ;
42
26
43
- this . $applePortalCookieService . updateUserSessionCookie ( sessionResponse . headers [ "set-cookie" ] ) ;
27
+ const sessionResponse = await this . $httpClient . httpRequest ( {
28
+ url : "https://appstoreconnect.apple.com/olympus/v1/session" ,
29
+ method : "GET" ,
30
+ headers : {
31
+ 'Cookie' : this . $applePortalCookieService . getUserSessionCookie ( )
32
+ }
33
+ } ) ;
34
+
35
+ this . $applePortalCookieService . updateUserSessionCookie ( sessionResponse . headers [ "set-cookie" ] ) ;
36
+ }
44
37
45
- const userDetailResponse = await this . $httpClient . httpRequest ( {
38
+ const userDetailsResponse = await this . $httpClient . httpRequest ( {
46
39
url : "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail" ,
47
40
method : "GET" ,
48
41
headers : {
@@ -51,9 +44,12 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
51
44
}
52
45
} ) ;
53
46
54
- this . $applePortalCookieService . updateUserSessionCookie ( userDetailResponse . headers [ "set-cookie" ] ) ;
47
+ this . $applePortalCookieService . updateUserSessionCookie ( userDetailsResponse . headers [ "set-cookie" ] ) ;
55
48
56
- return JSON . parse ( userDetailResponse . body ) . data ;
49
+ const userdDetails = JSON . parse ( userDetailsResponse . body ) . data ;
50
+ const result = { ...userdDetails , ...loginResult , userSessionCookie : this . $applePortalCookieService . getUserSessionCookie ( ) } ;
51
+
52
+ return result ;
57
53
}
58
54
59
55
public async createWebSession ( contentProviderId : number , dsId : string ) : Promise < string > {
@@ -79,6 +75,72 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
79
75
return webSessionCookie ;
80
76
}
81
77
78
+ private async login ( credentials : ICredentials , opts ?: IAppleCreateUserSessionOptions ) : Promise < IAppleLoginResult > {
79
+ const result = {
80
+ scnt : < string > null ,
81
+ xAppleIdSessionId : < string > null ,
82
+ isTwoFactorAuthenticationEnabled : false ,
83
+ areCredentialsValid : true
84
+ } ;
85
+
86
+ if ( opts && opts . sessionBase64 ) {
87
+ const decodedSession = Buffer . from ( opts . sessionBase64 , "base64" ) . toString ( "utf8" ) ;
88
+
89
+ this . $applePortalCookieService . updateUserSessionCookie ( [ decodedSession ] ) ;
90
+
91
+ result . isTwoFactorAuthenticationEnabled = decodedSession . indexOf ( "DES" ) > - 1 ;
92
+ } else {
93
+ try {
94
+ await this . loginCore ( credentials ) ;
95
+ } catch ( err ) {
96
+ const statusCode = err && err . response && err . response . statusCode ;
97
+ result . areCredentialsValid = statusCode !== 401 && statusCode !== 403 ;
98
+ result . isTwoFactorAuthenticationEnabled = statusCode === 409 ;
99
+ if ( result . isTwoFactorAuthenticationEnabled && opts && ! opts . applicationSpecificPassword ) {
100
+ this . $errors . failWithoutHelp ( `Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided.
101
+ To generate an application-specific password, please go to https://appleid.apple.com/account/manage.
102
+ This password will be used for the iTunes Transporter, which is used to upload your application.` ) ;
103
+ }
104
+
105
+ if ( result . isTwoFactorAuthenticationEnabled && opts && opts . ensureConsoleIsInteractive && ! isInteractive ( ) ) {
106
+ this . $errors . failWithoutHelp ( `Your account has two-factor authentication enabled, but your console is not interactive.
107
+ For more details how to set up your environment, please execute "tns publish ios --help".` ) ;
108
+ }
109
+
110
+ const headers = ( err && err . response && err . response . headers ) || { } ;
111
+ result . scnt = headers . scnt ;
112
+ result . xAppleIdSessionId = headers [ 'x-apple-id-session-id' ] ;
113
+ }
114
+ }
115
+
116
+ return result ;
117
+ }
118
+
119
+ private async loginCore ( credentials : ICredentials ) : Promise < void > {
120
+ const loginConfig = await this . getLoginConfig ( ) ;
121
+ const loginUrl = `${ loginConfig . authServiceUrl } /auth/signin` ;
122
+ const headers = {
123
+ 'Content-Type' : 'application/json' ,
124
+ 'X-Requested-With' : 'XMLHttpRequest' ,
125
+ 'X-Apple-Widget-Key' : loginConfig . authServiceKey ,
126
+ 'Accept' : 'application/json, text/javascript'
127
+ } ;
128
+ const body = JSON . stringify ( {
129
+ accountName : credentials . username ,
130
+ password : credentials . password ,
131
+ rememberMe : true
132
+ } ) ;
133
+
134
+ const loginResponse = await this . $httpClient . httpRequest ( {
135
+ url : loginUrl ,
136
+ method : "POST" ,
137
+ body,
138
+ headers
139
+ } ) ;
140
+
141
+ this . $applePortalCookieService . updateUserSessionCookie ( loginResponse . headers [ "set-cookie" ] ) ;
142
+ }
143
+
82
144
private async getLoginConfig ( ) : Promise < { authServiceUrl : string , authServiceKey : string } > {
83
145
let config = null ;
84
146
@@ -91,5 +153,46 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
91
153
92
154
return config || this . defaultLoginConfig ;
93
155
}
156
+
157
+ private async handleTwoFactorAuthentication ( scnt : string , xAppleIdSessionId : string , authServiceKey : string ) : Promise < void > {
158
+ const headers = {
159
+ 'scnt' : scnt ,
160
+ 'X-Apple-Id-Session-Id' : xAppleIdSessionId ,
161
+ 'X-Apple-Widget-Key' : authServiceKey ,
162
+ 'Accept' : 'application/json'
163
+ } ;
164
+ const authResponse = await this . $httpClient . httpRequest ( {
165
+ url : "https://idmsa.apple.com/appleauth/auth" ,
166
+ method : "GET" ,
167
+ headers
168
+ } ) ;
169
+
170
+ const data = JSON . parse ( authResponse . body ) ;
171
+ if ( data . trustedPhoneNumbers && data . trustedPhoneNumbers . length ) {
172
+ const parsedAuthResponse = JSON . parse ( authResponse . body ) ;
173
+ const token = await this . $prompter . getString ( `Please enter the ${ parsedAuthResponse . securityCode . length } digit code` , { allowEmpty : false } ) ;
174
+
175
+ await this . $httpClient . httpRequest ( {
176
+ url : `https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode` ,
177
+ method : "POST" ,
178
+ body : JSON . stringify ( {
179
+ securityCode : {
180
+ code : token . toString ( )
181
+ }
182
+ } ) ,
183
+ headers : { ...headers , 'Content-Type' : "application/json" }
184
+ } ) ;
185
+
186
+ const authTrustResponse = await this . $httpClient . httpRequest ( {
187
+ url : "https://idmsa.apple.com/appleauth/auth/2sv/trust" ,
188
+ method : "GET" ,
189
+ headers
190
+ } ) ;
191
+
192
+ this . $applePortalCookieService . updateUserSessionCookie ( authTrustResponse . headers [ "set-cookie" ] ) ;
193
+ } else {
194
+ this . $errors . failWithoutHelp ( `Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, NativeScript CLI don't know how to handle this response: ${ data } ` ) ;
195
+ }
196
+ }
94
197
}
95
198
$injector . register ( "applePortalSessionService" , ApplePortalSessionService ) ;
0 commit comments