"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CLIENT_CREDENTIALS = void 0;
const tslib_1 = require("tslib");
const _ = require("lodash");
const core_1 = require("@oclif/core");
const contracts_1 = require("../../apis/codiac-relay/contracts");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
const command_base_exec_tenant_1 = require("../../command-base-exec-tenant");
const config_ui_utils_1 = require("../../uilogic/config-ui-utils");
/** wtf: This because we cant use `CredentialType.client_credentials`.
 *
 * Using the enum `CredentialType.client_credentials` fails with:
 *
 * *"Cannot read properties of undefined (reading 'client_credentials')"*
 */
exports.CLIENT_CREDENTIALS = "client_credentials";
class CspLogin extends command_base_exec_tenant_1.CommandBaseExecTenant {
    // static readonly USERNAME_PLACEHOLDER = "<CspTenantClientId>";
    // protected bootstrap(): void {
    //   super.bootstrap();
    //   CloudProviderContextualizer.bootstrap(this.ioc);
    // }
    captureArgs() {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { args, flags } = yield this.parse(CspLogin);
            let partial = {};
            if (args.provider) {
                partial.providerCode = type_safety_utils_1.TypeSafetyUtils.toEnum(contracts_1.CloudProviderEnum, args.provider);
                if (partial.providerCode == undefined)
                    throw new Error("Unrecognized cloud provider.");
            }
            // TODO: We should build this with better decoupled principles 
            // (eg: delegate to a ui provider instead of if-aws), 
            // but not worth it until there's more than just this one single outlier.
            if (partial.providerCode == contracts_1.CloudProviderEnum.aws && flags['aws-session-token']) {
                partial.proprietaryInput = { sessionToken: flags['aws-session-token'] };
            }
            if (flags.subscriptionId)
                partial.providerSubscription = flags.subscriptionId.trim();
            if (flags.tenantId)
                partial.providerTenant = flags.tenantId.trim();
            if (flags.user) {
                partial.username = flags.user;
            }
            if (flags.clientSecret) {
                partial.credsType = exports.CLIENT_CREDENTIALS;
                // partial.username = partial.username ?? CspLogin.USERNAME_PLACEHOLDER; // <-- this is not a magic string, just a placeholder.
                if (flags.silent == true) {
                    // silent mode, with piped input
                    partial.secret = (_a = this.pipedInput) === null || _a === void 0 ? void 0 : _a.trim(); // (await readStdin())?.trim();
                    this.logger.debug("Secret captured from stdin stream in command [csp login].");
                    if (_.isEmpty(partial.secret)) {
                        throw new Error("Missing client secret.  In silent mode, clientSecret must be passed in via stdin (ie: piped input).");
                    }
                }
            }
            if (flags.password) {
                partial.secret = flags.password;
            }
            // ---------------------------------------------------------------
            // TODO: move everything from here down into the base class
            // ---------------------------------------------------------------
            const interactionMode = (this.pipedInput) ? config_ui_utils_1.InteractionModes.silent : (_c = (_b = undefined !== null && undefined !== void 0 ? undefined : (flags.echo ? config_ui_utils_1.InteractionModes.echo : undefined)) !== null && _b !== void 0 ? _b : (flags["to-script"] ? config_ui_utils_1.InteractionModes.toscript : undefined)) !== null && _c !== void 0 ? _c : (flags.silent ? config_ui_utils_1.InteractionModes.silent : undefined);
            const takeDefaults = flags['take-defaults'];
            return [partial, interactionMode, takeDefaults];
        });
    }
    buildCandidate_silently(partial, rootMap) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (partial.providerCode == undefined) {
                throw new Error(`You must specify a cloud provider.`);
            }
            if (_.isEmpty(partial.secret)) {
                throw new Error(`No clientSecret/password provided (clientSecret/password can be provided interatively or via the stdin process stream).`);
            }
            const cspTenant = yield this.cspContextualizer.ensureCspTenantContext(this._codiacApi, partial.providerCode, partial.providerSubscription, partial.providerTenant, true);
            partial.providerSubscription = cspTenant.subscriptionId;
            partial.providerTenant = cspTenant.tenantId;
            let candidate;
            if (partial.proprietaryInput) {
                candidate = this.cspContextualizer.assembleLoginRequest(this.ioc, partial.providerCode, partial.username, partial.secret, cspTenant.subscriptionId, partial.proprietaryInput);
                candidate.proprietaryInput = partial.proprietaryInput;
            }
            else {
                candidate = {
                    providerCode: partial.providerCode,
                    username: partial.username,
                    secret: partial.secret,
                    providerSubscription: partial.providerSubscription,
                };
                if (partial.providerTenant)
                    candidate.providerTenant = partial.providerTenant;
            }
            if (partial.credsType)
                candidate.credsType = partial.credsType;
            return candidate;
        });
    }
    buildCandidate_interactively(partial, takeDefaults, rootMap) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // Select the CSP
            if (partial.providerCode == undefined) {
                // Select interactively
                let allPvds = Object.values(contracts_1.CloudProviderEnum); //this.cspUiFactory.getAllClusterProviders(this.ioc);  // TODO: this call is currently limited to cluster providers - we need the comprehensive list of CSPs in use.
                _.remove(allPvds, x => x == contracts_1.CloudProviderEnum.other);
                _.remove(allPvds, x => x == contracts_1.CloudProviderEnum.localMachine);
                partial.providerCode = yield this.cspContextualizer.promptForCloudProvider(allPvds, undefined);
            }
            // prompt for password/secret if needed
            if (partial.credsType == exports.CLIENT_CREDENTIALS) {
                // Client Credentials auth flow (aka: service principal creds)
                while (partial.secret == undefined || _.isEmpty(partial.secret)) {
                    partial.secret = yield this._ui.promptPassword("clientSecret", "Client Secret: ");
                    if (_.isEmpty(partial.secret)) {
                        this._ui.userMessage(`No client secret provided (client secret can be provided interactively or via the stdin process stream).`);
                    }
                }
            }
            // Csp Tenant
            const cspTenant = yield this.cspContextualizer.ensureCspTenantContext(this._codiacApi, partial.providerCode, partial.providerSubscription, partial.providerTenant, false);
            partial.providerSubscription = cspTenant.subscriptionId;
            partial.providerTenant = cspTenant.tenantId;
            // ULTIMATE CANDIDATE
            let candidate = {
                providerCode: partial.providerCode,
                credsType: partial.credsType,
                username: partial.username,
                secret: partial.secret,
                providerSubscription: partial.providerSubscription,
                providerTenant: partial.providerTenant
            };
            return candidate;
        });
    }
    buildCommandString(candidate, cmd) {
        cmd.push(candidate.providerCode);
        cmd.push("--silent");
        if (candidate.username)
            cmd.push(...["--user", candidate.username]);
        if (candidate.credsType == exports.CLIENT_CREDENTIALS)
            cmd.push("--clientSecret");
        if (candidate.providerSubscription)
            cmd.push(...["--subscriptionId", candidate.providerSubscription]);
        if (candidate.providerTenant)
            cmd.push(...["--tenantId", candidate.providerTenant]);
        if (candidate.proprietaryInput) {
            // Aws
            if (candidate.proprietaryInput.sessionToken) {
                cmd.push(...["--aws-session-token", candidate.proprietaryInput.sessionToken]);
            }
        }
        // This command must run in silent mode, 
        // and even if they provided a password inline, 
        // we should only recommend piping the secret in.
        cmd.unshift("echo", "$MY_SECRET", "|");
        return cmd;
    }
    executeTask(candidate) {
        var _a, _b, _c, _d;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (candidate.credsType == exports.CLIENT_CREDENTIALS) {
                // ------------------------------------------------------------
                // Client Credentials auth flow (aka: service principal creds)
                // ------------------------------------------------------------
                try {
                    let result = yield this.genRelayClient.Call.csp_login(candidate);
                    if (result.success) {
                        this._ui.userMessage(`${this._styler.colorGood("Success!")} Authenticated this Codiac instance as a trusted client with ${this._styler.hilite(candidate.providerCode)} for [${this._userCntx.currentTenantCode}: ${this._userCntx.userName}].`);
                        this.logger.debug(`Authenticated this Codiac instance as a trusted client with ${this._styler.hilite(candidate.providerCode)} for [${this._userCntx.currentTenantCode}: ${this._userCntx.userName}].`);
                    }
                    else {
                        this._ui.userMessage(`${this._styler.colorCritical("Authentication FAILED")} (${result.message})`);
                    }
                }
                catch (err) {
                    this._ui.userMessage(`${this._styler.colorCritical("Error")} ${(_a = err.name) !== null && _a !== void 0 ? _a : ""} ${(_b = err.message) !== null && _b !== void 0 ? _b : ""}`);
                }
            }
            else {
                // ------------------------------------------------------------
                // User Interactive auth flow (aka: PKCE with regular user creds)
                // ------------------------------------------------------------
                try {
                    let cspUser = yield this.cspContextualizer.promptForCloudProviderCreds(this.ioc, candidate.providerCode, false, candidate.username, candidate.secret, { subscriptionId: candidate.providerSubscription, tenantId: candidate.providerTenant }, candidate.proprietaryInput);
                    let subx = "";
                    if (cspUser.subscriptionId) {
                        subx = ((cspUser.provider == contracts_1.CloudProviderEnum.aws) ? "acct" : "subx").concat(": ", cspUser.subscriptionId);
                        subx = "(".concat(subx, ")");
                    }
                    this._ui.userMessage(this._styler.colorGood("Success!").concat(" Authenticated with ", candidate.providerCode, " as ", this._styler.hilite(cspUser.username), subx));
                }
                catch (err) {
                    this._ui.userMessage(`${this._styler.colorCritical("FAILED")} ${(_c = err.name) !== null && _c !== void 0 ? _c : ""}${err.code ? " (".concat(err.code, ")") : ""} ${(_d = err.message) !== null && _d !== void 0 ? _d : ""}`);
                }
            }
            return;
        });
    }
}
exports.default = CspLogin;
CspLogin.description = 'Authenticates with the given Cloud Service Provider (CSP), eg: aws, azure, docker.  Use this command both to validate credentials and to declare the csp user account for subsequent commands involving that provider.';
CspLogin.examples = [
    'cod csp login',
    'cod csp login docker',
    'cat mypassword.txt | cod csp login -u me@mydomain.com --silent docker',
    'cat theclientsecret.txt | cod csp login --clientSecret --silent azure',
    'cat theclientsecret.txt | cod csp login --clientSecret --tenantId xy12pd34-a828-43d7-89c9-e52dg3cc2a7a --silent azure',
    'cat theclientsecret.txt | cod csp login --clientSecret --tenantId xy12pd34-a828-43d7-89c9-e52dg3cc2a7a -u abacab99-b1c2-45tu-m240gaddrac --silent azure',
    'cod csp login --clientSecret --tenantId xy12pd34-a828-43d7-89c9-e52dg3cc2a7a -u abacab99-b1c2-45tu-m240gaddrac azure',
    'cod csp login -u ASIAZ3XYZPDQ123 -p $(cat my-aws-secret-key.txt) aws',
    'cod csp login -u ASIAZ3XYZPDQ123 -p $(cat my-aws-secret-key.txt) --aws-session-token $(cat my-aws-sesh.txt) aws'
];
// static dependsOn = ["provider"];  // this isnt the whole of the requirements
CspLogin.flags = {
    user: core_1.Flags.string({ char: 'u', description: 'The username at the given cloud provider.' /*, exclusive: ["clientSecret"] */ }),
    password: core_1.Flags.string({ char: 'p', description: '(must be provided via stdin using --silent) The password at the given cloud provider.', exclusive: ["clientSecret"] }),
    "aws-session-token": core_1.Flags.string({ required: false, /* exclusive: ["clientSecret"], */ description: "(for AWS only) The session token parameter for use when authenticating with a short-term AWS access key credentials." }),
    clientSecret: core_1.Flags.boolean({ exclusive: ["password"], description: '(must be provided interactively or via stdin using --silent) Invokes the OAuth Client Credentials Flow. Used to authenticate when no user is present, such as an internal service or in a devops pipeline.  Note: This is a secret that must be created when Codiac gets registered as a trusted application in your cloud service provider subscription (eg: in azure, this is an app registration in Azure AD).' }),
    subscriptionId: core_1.Flags.string({ char: 's', description: '(in silent mode, either this or tenantId is required if you have more than one tenant profile on a given cloud provider) The Id of the cloud provider subscription under which to authenticate.' }),
    tenantId: core_1.Flags.string({ char: 't', description: '(in silent mode, either this or subscriptionId is required if you have more than one tenant profile on a given cloud provider) The Id of the cloud provider tenant under which to authenticate.' }),
    // TODO: Move these into the base class
    silent: core_1.Flags.boolean({ exclusive: ["echo", "to-script"], description: 'Non-Interactive mode; executes without any user interaction and fails on any missing or invalid arguments.' }),
    echo: core_1.Flags.boolean({ exclusive: ["to-script", "silent"], description: 'Echo interaction mode; renders the equivalent non-interactive cli command for future use before final execution.' }),
    "to-script": core_1.Flags.boolean({ exclusive: ["echo", "silent"], description: 'Toscript interaction mode; renders the equivalent non-interactive cli command WITHOUT any execution.' }),
    "take-defaults": core_1.Flags.boolean({ exclusive: ["silent"], description: "Setting this to true prevents prompting for confirmation on parameters that were passed in or were automatically set to default values (Irrelevant in --silent mode).  This behavior does NOT apply to scenarios that the command needs to override invalid user input; such cases will always prompt for confirmation.  eg: when no argument was provided for file or type, and only one exists, the system will render the autoselected file unless this flag is set." })
};
CspLogin.args = {
    provider: core_1.Args.string({ required: false, options: Object.keys(contracts_1.CloudProviderEnum), description: 'Code indicating the Cloud Service Provider (CSP) being authenticated with.' })
};
//# sourceMappingURL=login.js.map