1+ import { isInteractive } from "../../common/helpers" ;
2+
13export class ApplePortalSessionService implements IApplePortalSessionService {
24 private loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com" ;
35 private defaultLoginConfig = {
@@ -7,42 +9,33 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
79
810 constructor (
911 private $applePortalCookieService : IApplePortalCookieService ,
12+ private $errors : IErrors ,
1013 private $httpClient : Server . IHttpClient ,
11- private $logger : ILogger
14+ private $logger : ILogger ,
15+ private $prompter : IPrompter
1216 ) { }
1317
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 ) ;
3420
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 ) ;
4025 }
41- } ) ;
4226
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+ }
4437
45- const userDetailResponse = await this . $httpClient . httpRequest ( {
38+ const userDetailsResponse = await this . $httpClient . httpRequest ( {
4639 url : "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail" ,
4740 method : "GET" ,
4841 headers : {
@@ -51,9 +44,12 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
5144 }
5245 } ) ;
5346
54- this . $applePortalCookieService . updateUserSessionCookie ( userDetailResponse . headers [ "set-cookie" ] ) ;
47+ this . $applePortalCookieService . updateUserSessionCookie ( userDetailsResponse . headers [ "set-cookie" ] ) ;
5548
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 ;
5753 }
5854
5955 public async createWebSession ( contentProviderId : number , dsId : string ) : Promise < string > {
@@ -79,6 +75,72 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
7975 return webSessionCookie ;
8076 }
8177
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+
82144 private async getLoginConfig ( ) : Promise < { authServiceUrl : string , authServiceKey : string } > {
83145 let config = null ;
84146
@@ -91,5 +153,46 @@ export class ApplePortalSessionService implements IApplePortalSessionService {
91153
92154 return config || this . defaultLoginConfig ;
93155 }
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+ }
94197}
95198$injector . register ( "applePortalSessionService" , ApplePortalSessionService ) ;
0 commit comments