"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeyCloakClient = void 0;
const uuid_1 = require("uuid");
const localPort = require("local-port");
const winston = require("winston");
const utils = require("@cobalt-engine/utils");
const Request_1 = require("../../modules/Request");
const Config_1 = require("../../modules/Config");
const WizError_1 = require("../../modules/WizError");
const I18n_1 = require("../../modules/I18n");
const keycloakConnect = require("keycloak-connect");
const routes_1 = require("./routes");
const request = new Request_1.default();
const logedMessages = {
    title: 'Ewizard CLI Login Successful',
    message: 'You are logged in to the Ewizard Command-Line Interface.',
    info: 'You can close this window now <br>and continue using the CLI.',
};
const logoutMessages = {
    title: 'Ewizard CLI Logout Successful',
    message: 'You have logged out of the Ewizard Command-Line Interface.',
    info: 'You can close this window now.',
};
class KeyCloakClient {
    constructor(tokenHandler, callbackServer) {
        this.tokenHandler = tokenHandler;
        this.callbackServer = callbackServer;
    }
    login(data) {
        return this.getConfig()
            .then((config) => {
            const loginUrl = this.resolveUrl('token', config);
            const hasCredentials = (data.username && data.password);
            if (hasCredentials) {
                return this.loginWithCredentials(loginUrl, data, config);
            }
            return this.loginViaBrowser(loginUrl, config);
        });
    }
    logout() {
        return this.getConfig()
            .then((config) => this.logoutViaBrowser(config));
    }
    checkAuth() {
        return this.tokenHandler.get()
            .then(data => this.introspectToken(data.access_token))
            .then((active) => {
            if (!active) {
                return this.refreshTokenAndTryAgain(this.checkAuth.bind(this));
            }
        });
    }
    getConfig() {
        return Config_1.Config.get().then(result => result.authServer);
    }
    resolveUrl(route, config) {
        return encodeURI(utils.template(config.url + routes_1.default[route], {
            realm: config.realm,
        }));
    }
    loginWithCredentials(loginUrl, credentials, config) {
        const form = this.loginFormWithPassword(credentials, config);
        return this.getOfflineAccessTokens(loginUrl, form);
    }
    getOfflineAccessTokens(loginUrl, form) {
        const requestData = {
            form: utils.mixin({ scope: 'offline_access' }, form),
        };
        return request.post(loginUrl, requestData);
    }
    loginFormWithPassword(credentials, config) {
        return {
            client_id: config.clientId,
            client_secret: config.secret,
            grant_type: 'password',
            username: credentials.username,
            password: credentials.password,
        };
    }
    loginViaBrowser(loginUrl, config) {
        return this.retrieveAuthCode(config)
            .catch((err) => {
            throw new WizError_1.WizError('BROWSER_AUTH_CODE', err.message);
        })
            .then(({ code, redirectUrl }) => {
            const form = this.loginFormWithAuthCode(code, redirectUrl, config);
            return this.getOfflineAccessTokens(loginUrl, form);
        });
    }
    retrieveAuthCode(config) {
        const uniquieId = (0, uuid_1.v4)();
        let redirectUrl = '';
        return this.findOpenPort()
            .then((redirectPort) => {
            redirectUrl = this.callbackServer.createCallbackUrl(redirectPort);
            const url = this.createBrowserLoginUrl(config, uniquieId, redirectUrl);
            this.printBrowserUrl(url);
            return this.requestAuthCodeFromBrowser(url, redirectPort, uniquieId);
        })
            .then((code) => ({
            code,
            redirectUrl,
        }));
    }
    findOpenPort() {
        return new Promise((resolve, reject) => {
            localPort.findOpen(9005, (err, port) => {
                if (err) {
                    reject(new WizError_1.WizError('BROWSER_CALLBACK_PORT', err.message));
                    return;
                }
                resolve(port);
            });
        });
    }
    printBrowserUrl(url) {
        winston.info(`${I18n_1.default.info('LOGIN_URL')}  \n\n${url}\n`);
        winston.info(I18n_1.default.info('LOGIN_WAIT_BROWSER'));
    }
    createBrowserLogoutUrl(config, redirectUrl) {
        const keycloak = this.createBrowserUrlHelper(config);
        return keycloak.logoutUrl(redirectUrl);
    }
    createBrowserLoginUrl(config, clientUniquieId, redirectUrl) {
        const keycloak = this.createBrowserUrlHelper(config);
        return keycloak.loginUrl(clientUniquieId, redirectUrl);
    }
    createBrowserUrlHelper(config) {
        return new keycloakConnect({}, {
            realm: config.realm,
            'realm-public-key': config.realmPublicKey,
            'auth-server-url': config.url,
            'ssl-required': config.sslRequired,
            resource: config.clientId,
            credentials: {
                secret: config.secret,
            },
        });
    }
    requestAuthCodeFromBrowser(requestUrl, redirectPort, uniquieId) {
        return new Promise((resolve, reject) => {
            this.callbackServer.start(requestUrl, redirectPort, logedMessages, (url) => {
                const query = url.query;
                const isBrowserCallbackRequest = (query.state === uniquieId);
                if (isBrowserCallbackRequest) {
                    resolve(query.code);
                }
                else {
                    reject(new WizError_1.WizError('BROWSER_CALLBACK_UNEXPECTED_REQUEST', url.path));
                }
            }, (err) => {
                reject(new WizError_1.WizError('BROWSER_CALLBACK_FAILURE', err.message));
            });
        });
    }
    introspectToken(token) {
        return this.getConfig()
            .then((config) => {
            const introspectUrl = this.resolveUrl('introspect', config);
            const requestData = {
                form: {
                    token,
                    token_type_hint: 'access_token',
                },
                username: config.clientId,
                password: config.secret,
            };
            return request.post(introspectUrl, requestData)
                .then(result => result.active);
        })
            .catch((err) => {
            if (err.code === 'INVALID_REQUEST') {
                throw new WizError_1.WizError('NOT_LOGGED_IN');
            }
            throw err;
        });
    }
    requestLogoutFromBrowser(requestUrl, redirectPort) {
        return new Promise((resolve, reject) => {
            this.callbackServer.start(requestUrl, redirectPort, logoutMessages, () => {
                resolve();
            }, (err) => {
                reject(new WizError_1.WizError('BROWSER_CALLBACK_FAILURE', err.message));
            });
        });
    }
    loginFormWithAuthCode(code, redirectUrl, config) {
        return {
            code,
            client_id: config.clientId,
            client_secret: config.secret,
            grant_type: 'authorization_code',
            redirect_uri: redirectUrl,
        };
    }
    logoutViaBrowser(config) {
        return this.findOpenPort()
            .then((redirectPort) => {
            const redirectUrl = this.callbackServer.createCallbackUrl(redirectPort);
            const url = this.createBrowserLogoutUrl(config, redirectUrl);
            this.printBrowserUrl(url);
            return this.requestLogoutFromBrowser(url, redirectPort);
        });
    }
    refreshToken(data) {
        return this.getConfig()
            .then((config) => {
            const refreshUrl = this.resolveUrl('token', config);
            const requestData = {
                form: {
                    client_id: config.clientId,
                    client_secret: config.secret,
                    grant_type: 'refresh_token',
                    refresh_token: data.refresh_token,
                },
            };
            return request.post(refreshUrl, requestData);
        })
            .catch((err) => {
            if (err.code === 'INVALID_GRANT') {
                throw new WizError_1.WizError('NOT_LOGGED_IN');
            }
        });
    }
    refreshTokenAndTryAgain(action) {
        return this.tokenHandler.get()
            .then(this.refreshToken.bind(this))
            .then(this.tokenHandler.set.bind(this.tokenHandler))
            .then(this.tokenHandler.get.bind(this.tokenHandler))
            .then(action);
    }
}
exports.KeyCloakClient = KeyCloakClient;
//# sourceMappingURL=index.js.map