"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;
}
