"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const querystring_1 = __importDefault(require("querystring"));
const ts_log_debug_1 = require("ts-log-debug");
const crypto_1 = require("crypto");
const detect_port_1 = __importDefault(require("detect-port"));
const express_1 = __importDefault(require("express"));
const clime_1 = require("clime");
const configstore_1 = __importDefault(require("configstore"));
const opn_1 = __importDefault(require("opn"));
const HttpClient_1 = require("typed-rest-client/HttpClient");
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
const PORT_OPTIONS = [8888, 7777, 6666, 1337];
const CALLBACK_ROOT = 'http://localhost:';
const CLIENT_ID = 'KOrBkAlpYgN0H5x1O37Oz491JP445UBn';
const API_ROOT = 'https://modelnest.auth0.com';
const LOGIN_ROOT = `${API_ROOT}/authorize`;
const TOKEN_ROOT = `${API_ROOT}/oauth/token`;
const SUCCESS_URL = 'https://assets.entrepreneur.com/content/3x2/2000/20150724190206-success-focus-life-achievements.jpeg?width=700&crop=2:1';
const ERROR_URL = 'https://i.kym-cdn.com/entries/icons/mobile/000/000/028/Fail-Stamp-Transparent_copy.jpg';
const JWKS_URL = `${API_ROOT}/.well-known/jwks.json`;
const KID = 'RTlBMDhGNUZCODA1RENGMUU1MDFGMTBBQTdEODRGNjBCQUY4NDdFQw';
class Auth {
    constructor() {
        this.client = new HttpClient_1.HttpClient('model-nest-cli');
        this.jwksClient = jwks_rsa_1.default({
            jwksUri: JWKS_URL,
        });
        this.verifier = this.base64URLEncode(crypto_1.randomBytes(32));
        this.state = this.base64URLEncode(crypto_1.randomBytes(16));
        this.challenge = this.base64URLEncode(this.sha256(this.verifier));
        this.scopes = 'openid offline_access';
        this.responseType = 'code';
        this.configStore = new configstore_1.default('model-nest-cli');
    }
    base64URLEncode(str) {
        return str.toString('base64')
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }
    sha256(buffer) {
        return crypto_1.createHash('sha256').update(buffer).digest();
    }
    async getAvailablePort() {
        let port;
        for (const portOption of PORT_OPTIONS) {
            const detected = await detect_port_1.default(portOption);
            ts_log_debug_1.$log.debug(`Detected port ${detected} option ${portOption}`);
            if (detected === portOption) {
                port = portOption;
                break;
            }
        }
        if (!port) {
            throw new clime_1.ExpectedError('Unable to find open port for login flow');
        }
        return port;
    }
    async getLoginUrl() {
        const params = {
            response_type: this.responseType,
            code_challenge: this.challenge,
            code_challenge_method: 'S256',
            client_id: CLIENT_ID,
            redirect_uri: this.getCallbackUrl(),
            scope: this.scopes,
            audience: 'https://api.modelnest.io',
            state: this.state,
        };
        const paramUrl = querystring_1.default.stringify(params);
        const url = `${LOGIN_ROOT}?${paramUrl}`;
        return url;
    }
    getCallbackUrl() {
        return `${CALLBACK_ROOT}${this.port}/callback`;
    }
    async verifyToken(token) {
        const publicKey = await this.getPublicKey();
        jsonwebtoken_1.default.verify(token, publicKey, { algorithms: ['RS256'] });
        return true;
    }
    async getPublicKey() {
        return new Promise((resolve, reject) => {
            this.jwksClient.getSigningKey(KID, (err, key) => {
                if (err)
                    return reject(err);
                const signingKey = key.publicKey || key.rsaPublicKey;
                resolve(signingKey);
            });
        });
    }
    async refresh() {
        const currentToken = this.configStore.get('auth');
        const body = {
            grant_type: 'refresh_token',
            client_id: CLIENT_ID,
            refresh_token: currentToken.refresh_token,
        };
        const headers = { 'content-type': 'application/json' };
        const resp = await this.client.post(TOKEN_ROOT, JSON.stringify(body), headers);
        const token = await resp.readBody();
        ts_log_debug_1.$log.debug(JSON.parse(token));
        this.configStore.set('auth', JSON.parse(token));
        return true;
    }
    async getToken() {
        let currentToken = this.configStore.get('auth');
        try {
            await this.verifyToken(currentToken.access_token);
            return currentToken.access_token;
        }
        catch (err) {
            await this.refresh();
            currentToken = this.configStore.get('auth');
            return currentToken.access_token;
        }
    }
    handleError(message, res, reject) {
        ts_log_debug_1.$log.error(message);
        res.redirect(301, ERROR_URL);
        reject(new clime_1.ExpectedError('Login failed'));
    }
    async login() {
        const app = express_1.default();
        this.port = await this.getAvailablePort();
        const url = await this.getLoginUrl();
        console.log(url);
        opn_1.default(url, { wait: false });
        this.server = app.listen(this.port);
        return new Promise((resolve, reject) => {
            app.get('/callback', async (req, res) => {
                const error = req.query.error;
                const code = req.query.code;
                const state = req.query.state;
                if (error) {
                    this.handleError(req.query.error_description, res, reject);
                }
                const body = {
                    grant_type: 'authorization_code',
                    code_verifier: this.verifier,
                    code: code,
                    client_id: CLIENT_ID,
                    redirect_uri: this.getCallbackUrl(),
                };
                const headers = { 'content-type': 'application/json' };
                try {
                    const resp = await this.client.post(TOKEN_ROOT, JSON.stringify(body), headers);
                    const authString = await resp.readBody();
                    ts_log_debug_1.$log.debug(JSON.parse(authString));
                    const auth = JSON.parse(authString);
                    await this.verifyToken(auth.access_token);
                    if (auth.access_token) {
                        this.configStore.set('auth', auth);
                    }
                    res.redirect(301, SUCCESS_URL);
                }
                catch (err) {
                    this.handleError(err.message, res, reject);
                }
                this.server.close();
            });
        });
    }
}
exports.default = Auth;
//# sourceMappingURL=auth.js.map