UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

289 lines • 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApplePortalSessionService = void 0; const helpers_1 = require("../../common/helpers"); const yok_1 = require("../../common/yok"); const crypto = require("crypto"); const srp_wrapper_1 = require("./srp/srp-wrapper"); class ApplePortalSessionService { constructor($applePortalCookieService, $errors, $httpClient, $logger, $prompter) { this.$applePortalCookieService = $applePortalCookieService; this.$errors = $errors; this.$httpClient = $httpClient; this.$logger = $logger; this.$prompter = $prompter; this.loginConfigEndpoint = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"; this.defaultLoginConfig = { authServiceUrl: "https://idmsa.apple.com/appleautcodh", authServiceKey: "e0b80c3bf78523bfe80974d320935bfa30add02e1bff88ec2166c6bd5a706c42", }; } async createUserSession(credentials, opts) { const loginResult = await this.login(credentials, opts); if (!opts || !opts.sessionBase64) { if (loginResult.isTwoFactorAuthenticationEnabled) { const authServiceKey = (await this.getLoginConfig()).authServiceKey; await this.handleTwoFactorAuthentication(loginResult.scnt, loginResult.xAppleIdSessionId, authServiceKey, loginResult.hashcash); } const sessionResponse = await this.$httpClient.httpRequest({ url: "https://appstoreconnect.apple.com/olympus/v1/session", method: "GET", headers: { Cookie: this.$applePortalCookieService.getUserSessionCookie(), }, }); this.$applePortalCookieService.updateUserSessionCookie(sessionResponse.headers["set-cookie"]); } const userDetailsResponse = await this.$httpClient.httpRequest({ url: "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/ra/user/detail", method: "GET", headers: { "Content-Type": "application/json", Cookie: this.$applePortalCookieService.getUserSessionCookie(), }, }); this.$applePortalCookieService.updateUserSessionCookie(userDetailsResponse.headers["set-cookie"]); const userdDetails = JSON.parse(userDetailsResponse.body).data; const result = { ...userdDetails, ...loginResult, userSessionCookie: this.$applePortalCookieService.getUserSessionCookie(), }; return result; } async createWebSession(contentProviderId) { const webSessionResponse = await this.$httpClient.httpRequest({ url: "https://appstoreconnect.apple.com/olympus/v1/session", method: "POST", body: { provider: { providerId: contentProviderId, }, }, headers: { Accept: "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br", "X-Csrf-Itc": "itc", "Content-Type": "application/json;charset=UTF-8", "X-Requested-With": "olympus-ui", Cookie: this.$applePortalCookieService.getUserSessionCookie(), }, }); const webSessionCookie = this.$applePortalCookieService.getWebSessionCookie(webSessionResponse.headers["set-cookie"]); return webSessionCookie; } async login(credentials, opts) { var _a, _b; const result = { scnt: null, xAppleIdSessionId: null, isTwoFactorAuthenticationEnabled: false, areCredentialsValid: true, hashcash: null, }; if (opts && opts.sessionBase64) { const decodedSession = Buffer.from(opts.sessionBase64, "base64").toString("utf8"); this.$applePortalCookieService.updateUserSessionCookie([decodedSession]); result.isTwoFactorAuthenticationEnabled = decodedSession.indexOf("DES") > -1; } else { try { await this.loginCore(credentials); } catch (err) { const statusCode = err && err.response && err.response.status; const bits = (_a = err === null || err === void 0 ? void 0 : err.response) === null || _a === void 0 ? void 0 : _a.headers["x-apple-hc-bits"]; const challenge = (_b = err === null || err === void 0 ? void 0 : err.response) === null || _b === void 0 ? void 0 : _b.headers["x-apple-hc-challenge"]; const hashcash = makeHashCash(bits, challenge); result.hashcash = hashcash; result.areCredentialsValid = statusCode !== 401 && statusCode !== 403; result.isTwoFactorAuthenticationEnabled = statusCode === 409; if (result.isTwoFactorAuthenticationEnabled && opts && opts.requireApplicationSpecificPassword && !opts.applicationSpecificPassword) { this.$errors .fail(`Your account has two-factor authentication enabled but --appleApplicationSpecificPassword option is not provided. To generate an application-specific password, please go to https://appleid.apple.com/account/manage. This password will be used for the iTunes Transporter, which is used to upload your application.`); } if (result.isTwoFactorAuthenticationEnabled && opts && opts.requireInteractiveConsole && !(0, helpers_1.isInteractive)()) { this.$errors .fail(`Your account has two-factor authentication enabled, but your console is not interactive. For more details how to set up your environment, please execute "ns publish ios --help".`); } const headers = (err && err.response && err.response.headers) || {}; result.scnt = headers.scnt; result.xAppleIdSessionId = headers["x-apple-id-session-id"]; } } return result; } async loginCore(credentials) { const wrapper = new srp_wrapper_1.GSASRPAuthenticator(credentials.username); const initData = await wrapper.getInit(); const loginConfig = await this.getLoginConfig(); const loginUrl = `${loginConfig.authServiceUrl}/auth/signin/init`; const headers = { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", "X-Apple-Widget-Key": loginConfig.authServiceKey, Accept: "application/json, text/javascript", }; const initResponse = await this.$httpClient.httpRequest({ url: loginUrl, method: "POST", body: initData, headers, }); const body = JSON.parse(initResponse.response.body); const completeData = await wrapper.getComplete(credentials.password, body); const hashcash = await this.fetchHashcash(loginConfig.authServiceUrl, loginConfig.authServiceKey); const completeUrl = `${loginConfig.authServiceUrl}/auth/signin/complete?isRememberMeEnabled=false`; const completeHeaders = { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", "X-Apple-Widget-Key": loginConfig.authServiceKey, Accept: "application/json, text/javascript", "X-Apple-HC": hashcash || "", }; const completeResponse = await this.$httpClient.httpRequest({ url: completeUrl, method: "POST", completeHeaders, body: completeData, headers: completeHeaders, }); this.$applePortalCookieService.updateUserSessionCookie(completeResponse.headers["set-cookie"]); } async getLoginConfig() { let config = null; try { const response = await this.$httpClient.httpRequest({ url: this.loginConfigEndpoint, method: "GET", }); config = JSON.parse(response.body); } catch (err) { this.$logger.trace(`Error while executing request to ${this.loginConfigEndpoint}. More info: ${err}`); } return config || this.defaultLoginConfig; } async fetchHashcash(authServiceUrl, authServiceKey) { const loginUrl = `${authServiceUrl}/auth/signin?widgetKey=${authServiceKey}`; const response = await this.$httpClient.httpRequest({ url: loginUrl, method: "GET", }); const headers = response.headers; const bits = headers["X-Apple-HC-Bits"]; const challenge = headers["X-Apple-HC-Challenge"]; return makeHashCash(bits, challenge); } async handleTwoFactorAuthentication(scnt, xAppleIdSessionId, authServiceKey, hashcash) { const headers = { scnt: scnt, "X-Apple-Id-Session-Id": xAppleIdSessionId, "X-Apple-Widget-Key": authServiceKey, "X-Apple-HC": hashcash, Accept: "application/json", }; const authResponse = await this.$httpClient.httpRequest({ url: "https://idmsa.apple.com/appleauth/auth", method: "GET", headers, }); const data = JSON.parse(authResponse.body); const isSMS = data.trustedPhoneNumbers && data.trustedPhoneNumbers.length === 1 && data.noTrustedDevices; // 1 device and no trusted devices means sms was automatically sent. const multiSMS = data.trustedPhoneNumbers && data.trustedPhoneNumbers.length !== 1 && data.noTrustedDevices; // Not handling more than 1 sms device and no trusted devices. let token; if (data.trustedPhoneNumbers && data.trustedPhoneNumbers.length && !multiSMS) { const parsedAuthResponse = JSON.parse(authResponse.body); token = await this.$prompter.getString(`Please enter the ${parsedAuthResponse.securityCode.length} digit code`, { allowEmpty: false }); const body = { securityCode: { code: token.toString(), }, }; let url = `https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode`; if (isSMS) { // No trusted devices means it must be sms. body.mode = "sms"; body.phoneNumber = { id: data.trustedPhoneNumbers[0].id, }; url = `https://idmsa.apple.com/appleauth/auth/verify/phone/securitycode`; } await this.$httpClient.httpRequest({ url, method: "POST", body, headers: { ...headers, "Content-Type": "application/json" }, }); const authTrustResponse = await this.$httpClient.httpRequest({ url: "https://idmsa.apple.com/appleauth/auth/2sv/trust", method: "GET", headers, }); this.$applePortalCookieService.updateUserSessionCookie(authTrustResponse.headers["set-cookie"]); } else if (multiSMS) { this.$errors.fail(`The NativeScript CLI does not support SMS authenticaton with multiple registered phone numbers.`); } else { this.$errors.fail(`Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, NativeScript CLI don't know how to handle this response: ${data}`); } } } exports.ApplePortalSessionService = ApplePortalSessionService; yok_1.injector.register("applePortalSessionService", ApplePortalSessionService); function makeHashCash(bits, challenge) { const version = 1; const dateString = getHashCanDateString(); let result; for (let counter = 0;; counter++) { const hc = [version, bits, dateString, challenge, `:${counter}`].join(":"); const shasumData = crypto.createHash("sha1"); shasumData.update(hc); const digest = shasumData.digest(); if (checkBits(+bits, digest)) { result = hc; break; } } return result; } function getHashCanDateString() { const now = new Date(); return `${now.getFullYear()}${padTo2Digits(now.getMonth() + 1)}${padTo2Digits(now.getDate())}${padTo2Digits(now.getHours())}${padTo2Digits(now.getMinutes())}${padTo2Digits(now.getSeconds())}`; } function padTo2Digits(num) { return num.toString().padStart(2, "0"); } function checkBits(bits, digest) { let result = true; for (let i = 0; i < bits; ++i) { result = checkBit(i, digest); if (!result) break; } return result; } function checkBit(position, buffer) { const bitOffset = position & 7; // in byte const byteIndex = position >> 3; // in buffer const bit = (buffer[byteIndex] >> bitOffset) & 1; return bit === 0; } //# sourceMappingURL=apple-portal-session-service.js.map