nativescript
Version:
Command-line interface for building NativeScript projects
288 lines (287 loc) • 13.1 kB
JavaScript
"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;
const multiSMS = data.trustedPhoneNumbers &&
data.trustedPhoneNumbers.length !== 1 &&
data.noTrustedDevices;
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) {
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;
const byteIndex = position >> 3;
const bit = (buffer[byteIndex] >> bitOffset) & 1;
return bit === 0;
}