"use strict";
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.NtlmCredentialHandler = void 0;
const http = require("http");
const https = require("https");
const _ = require("underscore");
const ntlm = require("../opensource/Node-SMB/lib/ntlm");
class NtlmCredentialHandler {
    constructor(username, password, workstation, domain) {
        this._ntlmOptions = {};
        this._ntlmOptions.username = username;
        this._ntlmOptions.password = password;
        this._ntlmOptions.domain = domain || '';
        this._ntlmOptions.workstation = workstation || '';
    }
    prepareRequest(options) {
        // No headers or options need to be set.  We keep the credentials on the handler itself.
        // If a (proxy) agent is set, remove it as we don't support proxy for NTLM at this time
        if (options.agent) {
            delete options.agent;
        }
    }
    canHandleAuthentication(response) {
        if (response && response.message && response.message.statusCode === 401) {
            // Ensure that we're talking NTLM here
            // Once we have the www-authenticate header, split it so we can ensure we can talk NTLM
            const wwwAuthenticate = response.message.headers['www-authenticate'];
            return wwwAuthenticate && (wwwAuthenticate.split(', ').indexOf("NTLM") >= 0);
        }
        return false;
    }
    handleAuthentication(httpClient, requestInfo, objs) {
        return new Promise((resolve, reject) => {
            const callbackForResult = function (err, res) {
                if (err) {
                    reject(err);
                    return;
                }
                // We have to readbody on the response before continuing otherwise there is a hang.
                res.readBody().then(() => {
                    resolve(res);
                });
            };
            this.handleAuthenticationPrivate(httpClient, requestInfo, objs, callbackForResult);
        });
    }
    handleAuthenticationPrivate(httpClient, requestInfo, objs, finalCallback) {
        // Set up the headers for NTLM authentication
        requestInfo.options = _.extend(requestInfo.options, {
            username: this._ntlmOptions.username,
            password: this._ntlmOptions.password,
            domain: this._ntlmOptions.domain,
            workstation: this._ntlmOptions.workstation
        });
        requestInfo.options.agent = httpClient.isSsl ?
            new https.Agent({ keepAlive: true }) :
            new http.Agent({ keepAlive: true });
        let self = this;
        // The following pattern of sending the type1 message following immediately (in a setImmediate) is
        // critical for the NTLM exchange to happen.  If we removed setImmediate (or call in a different manner)
        // the NTLM exchange will always fail with a 401.
        this.sendType1Message(httpClient, requestInfo, objs, function (err, res) {
            if (err) {
                return finalCallback(err, null, null);
            }
            /// We have to readbody on the response before continuing otherwise there is a hang.
            res.readBody().then(() => {
                // It is critical that we have setImmediate here due to how connection requests are queued.
                // If setImmediate is removed then the NTLM handshake will not work.
                // setImmediate allows us to queue a second request on the same connection. If this second
                // request is not queued on the connection when the first request finishes then node closes
                // the connection. NTLM requires both requests to be on the same connection so we need this.
                setImmediate(function () {
                    self.sendType3Message(httpClient, requestInfo, objs, res, finalCallback);
                });
            });
        });
    }
    // The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js
    sendType1Message(httpClient, requestInfo, objs, finalCallback) {
        const type1HexBuffer = ntlm.encodeType1(this._ntlmOptions.workstation, this._ntlmOptions.domain);
        const type1msg = `NTLM ${type1HexBuffer.toString('base64')}`;
        const type1options = {
            headers: {
                'Connection': 'keep-alive',
                'Authorization': type1msg
            },
            timeout: requestInfo.options.timeout || 0,
            agent: requestInfo.httpModule,
        };
        const type1info = {};
        type1info.httpModule = requestInfo.httpModule;
        type1info.parsedUrl = requestInfo.parsedUrl;
        type1info.options = _.extend(type1options, _.omit(requestInfo.options, 'headers'));
        return httpClient.requestRawWithCallback(type1info, objs, finalCallback);
    }
    // The following method is an adaptation of code found at https://github.com/SamDecrock/node-http-ntlm/blob/master/httpntlm.js
    sendType3Message(httpClient, requestInfo, objs, res, callback) {
        if (!res.message.headers && !res.message.headers['www-authenticate']) {
            throw new Error('www-authenticate not found on response of second request');
        }
        /**
         * Server will respond with challenge/nonce
         * assigned to response's "WWW-AUTHENTICATE" header
         * and should adhere to RegExp /^NTLM\s+(.+?)(,|\s+|$)/
         */
        const serverNonceRegex = /^NTLM\s+(.+?)(,|\s+|$)/;
        const serverNonce = Buffer.from((res.message.headers['www-authenticate'].match(serverNonceRegex) || [])[1], 'base64');
        let type2msg;
        /**
         * Wrap decoding the Server's challenge/nonce in
         * try-catch block to throw more comprehensive
         * Error with clear message to consumer
         */
        try {
            type2msg = ntlm.decodeType2(serverNonce);
        }
        catch (error) {
            throw new Error(`Decoding Server's Challenge to Obtain Type2Message failed with error: ${error.message}`);
        }
        const type3msg = ntlm.encodeType3(this._ntlmOptions.username, this._ntlmOptions.workstation, this._ntlmOptions.domain, type2msg, this._ntlmOptions.password).toString('base64');
        const type3options = {
            headers: {
                'Authorization': `NTLM ${type3msg}`,
                'Connection': 'Close'
            },
            agent: requestInfo.httpModule,
        };
        const type3info = {};
        type3info.httpModule = requestInfo.httpModule;
        type3info.parsedUrl = requestInfo.parsedUrl;
        type3options.headers = _.extend(type3options.headers, requestInfo.options.headers);
        type3info.options = _.extend(type3options, _.omit(requestInfo.options, 'headers'));
        return httpClient.requestRawWithCallback(type3info, objs, callback);
    }
}
exports.NtlmCredentialHandler = NtlmCredentialHandler;
