ews-javascript-api-auth
Version:
Library to Help with non-Basic Authentication methods with ews-javascript-api package
187 lines (152 loc) • 7.73 kB
text/typescript
import { FetchStream, fetchUrl } from 'fetch';
import * as Promise from "bluebird";
import { IXHROptions, IXHRApi, IXHRProgress } from "./ews.partial";
import { setupXhrResponse } from "./utils";
import { Agent as httpsAgent } from "https";
var { createType1Message, decodeType2Message, createType3Message } = require("ntlm-client") //ref: has NTLM v2 support // info: also possible to use this package in node.
//var ntlm = require('httpntlm').ntlm; //removing httpntlm due to lack of NTLM v2
// var HttpsAgent = require('agentkeepalive').HttpsAgent; // can use this instead of node internal http agent
// var keepaliveAgent = new HttpsAgent(); // new HttpsAgent({ keepAliveMsecs :10000}); need to add more seconds to keepalive for debugging time. debugging is advised on basic auth only
/** @internal */
export class ntlmAuthXhrApi implements IXHRApi {
private stream: FetchStream = null;
private username: string = null;
private password: string = null;
private domain: string = '';
private allowUntrustedCertificate: boolean;
get apiName(): string {
return "ntlm";
}
constructor(username: string, password: string, allowUntrustedCertificate: boolean = false) {
this.username = username || '';
this.password = password || '';
this.allowUntrustedCertificate = allowUntrustedCertificate;
if (username.indexOf("\\") > 0) {
this.username = username.split("\\")[1];
this.domain = username.split("\\")[0].toUpperCase();
}
}
xhr(xhroptions: IXHROptions, progressDelegate?: (progressData: IXHRProgress) => void): Promise<XMLHttpRequest> {
//setup xhr for github.com/andris9/fetch options
let options = {
url: xhroptions.url,
//payload: xhroptions.data,
headers: xhroptions.headers,
method: 'GET',
agent: new httpsAgent({ keepAlive: true, rejectUnauthorized: !this.allowUntrustedCertificate }) //keepaliveAgent
}
return new Promise<XMLHttpRequest>((resolve, reject) => {
this.ntlmPreCall(options).then((optionsWithNtlmHeader) => {
optionsWithNtlmHeader['payload'] = xhroptions.data;
optionsWithNtlmHeader['method'] = <any>xhroptions.type
fetchUrl(xhroptions.url, optionsWithNtlmHeader, (error, meta, body) => {
if (error) {
reject(error);
}
else {
let xhrResponse: XMLHttpRequest = <any>{
response: body.toString(),
status: meta.status,
redirectCount: meta.redirectCount,
headers: meta.responseHeaders,
finalUrl: meta.finalUrl,
responseType: '',
statusText: undefined,
};
if (xhrResponse.status === 200) {
resolve(setupXhrResponse(xhrResponse));
}
else {
reject(setupXhrResponse(xhrResponse));
}
}
});
}, reject);
})
}
xhrStream(xhroptions: IXHROptions, progressDelegate: (progressData: IXHRProgress) => void): Promise<XMLHttpRequest> {
//setup xhr for github.com/andris9/fetch options
let options = {
url: xhroptions.url,
//payload: xhroptions.data,
headers: xhroptions.headers,
method: 'GET',
agent: new httpsAgent({ keepAlive: true, rejectUnauthorized: !this.allowUntrustedCertificate }) //keepaliveAgent
}
return new Promise<XMLHttpRequest>((resolve, reject) => {
this.ntlmPreCall(options).then((optionsWithNtlmHeader) => {
optionsWithNtlmHeader['payload'] = xhroptions.data;
optionsWithNtlmHeader['method'] = <any>xhroptions.type
this.stream = new FetchStream(xhroptions.url, optionsWithNtlmHeader);
this.stream.on("data", (chunk) => {
//console.log(chunk.toString());
progressDelegate({ type: "data", data: chunk.toString() });
});
this.stream.on("meta", (meta) => {
progressDelegate({ type: "header", headers: meta["responseHeaders"] });
});
this.stream.on("end", () => {
progressDelegate({ type: "end" });
resolve();
});
this.stream.on('error', (error) => {
progressDelegate({ type: "error", error: error });
this.disconnect();
reject(error);
});
}, reject);
});
}
disconnect() {
if (this.stream) {
try {
this.stream.destroy();
}
catch (e) { }
}
}
private ntlmPreCall(options: IXHROptions, ) {
let ntlmOptions = {
url: options.url,
username: this.username,
password: this.password,
workstation: options['workstation'] || '',
domain: this.domain
};
return new Promise<XMLHttpRequest>((resolve, reject) => {
//let type1msg = ntlm.createType1Message(ntlmOptions); //lack of v2
let type1msg = createType1Message(ntlmOptions.workstation, ntlmOptions.domain); // alternate client - ntlm-client
options.headers['Authorization'] = type1msg;
options.headers['Connection'] = 'keep-alive';
fetchUrl(options.url, options, (error, meta, body) => {
if (error) {
reject(error);
}
else {
let xhrResponse: XMLHttpRequest = <any>{
response: body,
status: meta.status,
redirectCount: meta.redirectCount,
headers: meta.responseHeaders,
finalUrl: meta.finalUrl,
responseType: '',
statusText: undefined,
};
resolve(xhrResponse);
}
});
}).then((res: any) => {
if (!res.headers['www-authenticate'])
throw new Error('www-authenticate not found on response of second request');
//let type2msg = ntlm.parseType2Message(res.headers['www-authenticate']); //httpntlm
//let type3msg = ntlm.createType3Message(type2msg, ntlmOptions); //httpntlm
let type2msg = decodeType2Message(res.headers['www-authenticate']); //with ntlm-client
let type3msg = createType3Message(type2msg, ntlmOptions.username, ntlmOptions.password, ntlmOptions.workstation, ntlmOptions.domain); //with ntlm-client
delete options.headers['authorization'] // 'fetch' has this wired addition with lower case, with lower case ntlm on server side fails
delete options.headers['connection'] // 'fetch' has this wired addition with lower case, with lower case ntlm on server side fails
options.headers['Authorization'] = type3msg;
options.headers['Connection'] = 'Close';
return options;
});
}
}