react-native-digest-fetch
Version:
Fetch which responds to digest challenges, built for react native
180 lines (148 loc) • 5.79 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.setNextNonceCount = setNextNonceCount;
exports.padStart = padStart;
exports.getNextNonceCount = getNextNonceCount;
exports.omitNullValues = omitNullValues;
exports.quoteIfRelevant = quoteIfRelevant;
exports.getDigestHeaderValue = getDigestHeaderValue;
exports.default = fetchWithDigest;
var _cryptoJs = require('crypto-js');
var _cryptoJs2 = _interopRequireDefault(_cryptoJs);
var _url = require('url');
var _url2 = _interopRequireDefault(_url);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// If the user's environment already includes fetch, we want to use it
if (typeof window !== 'undefined' && typeof window.fetch === 'undefined') {
window.fetch = require('node-fetch').default;
}
if (typeof global !== 'undefined' && typeof global.fetch === 'undefined') {
global.fetch = require('node-fetch').default;
}
function keys(object) {
var result = [];
for (var key in object) {
if (object.hasOwnProperty(key)) {
result.push(key);
}
}
return result;
}
function pick(object, whitelist) {
var result = {};
var keylength = whitelist.length;
for (var keyIndex = 0; keyIndex < keylength; keyIndex += 1) {
result[whitelist[keyIndex]] = object[whitelist[keyIndex]];
}
return result;
}
/**
* Pass the server's www-authenticate header
* @param {*} digestChallenge
*/
function getDigestChallengeParts(digestChallenge) {
var prefix = 'Digest ';
var challenge = digestChallenge.substr(digestChallenge.indexOf(prefix) + prefix.length);
var challengeArray = challenge.split(',');
return challengeArray.reduce(function (result, challengeItem) {
var splitPart = challengeItem.match(/^\s*?([a-zA-Z0-0]+)=("?(.*)"?|MD5|MD5-sess|token|TRUE|FALSE)\s*?$/);
if (splitPart.length > 2) {
result[splitPart[1]] = splitPart[2].replace(/\"/g, '');
}
return result;
}, {});
}
function setNextNonceCount(nextId) {
getNextNonceCount.nonceCount = nextId;
}
function padStart(string, length, paddingCharacter) {
if (string.length < length) {
return paddingCharacter.repeat(length - string.length) + string;
}
return string;
}
/**
* Incremented nonce used in responses to server challenges
*/
function getNextNonceCount() {
if (typeof getNextNonceCount.nonceCount === 'undefined') {
getNextNonceCount.nonceCount = 0;
}
getNextNonceCount.nonceCount = ((getNextNonceCount.nonceCount || 0) + 1) % 100000000;
return padStart('' + getNextNonceCount.nonceCount, 8, '0');
}
function omitNullValues(data) {
return keys(data).reduce(function (result, key) {
if (data[key] !== null) result[key] = data[key];
return result;
}, {});
}
/**
* Both the nc and key parameters are expected to be sent without quotes
* @param {*} object
* @param {*} key
*/
function quoteIfRelevant(object, key) {
return key === 'nc' || key === 'qop' ? '' + object[key] : '"' + object[key] + '"';
}
/**
* Get the authorization header value `Authorization: Digest XXXXX`, we want XXXXX
* @param {*} digestChallenge
* @param {*} param1
*/
function getDigestHeaderValue(digestChallenge, _ref) {
var url = _ref.url,
method = _ref.method,
headers = _ref.headers,
username = _ref.username,
password = _ref.password;
var parsed = _url2.default.parse(url);
var path = parsed.path;
var challengeParts = getDigestChallengeParts(digestChallenge);
var authHash = _cryptoJs2.default.MD5([username, challengeParts.realm, password].join(':'));
var pathHash = _cryptoJs2.default.MD5([method, path].join(':'));
var cnonce = null;
var nonce_count = null;
if (typeof challengeParts.qop === 'string') {
cnonce = _cryptoJs2.default.MD5(Math.random().toString(36)).toString(_cryptoJs2.default.enc.Hex).substr(0, 8);
nonce_count = getNextNonceCount();
}
var responseParams = [authHash.toString(_cryptoJs2.default.enc.Hex), challengeParts.nonce].concat(cnonce ? [nonce_count, cnonce] : []).concat([challengeParts.qop, pathHash.toString(_cryptoJs2.default.enc.Hex)]);
var authParams = omitNullValues(_extends({}, pick(challengeParts, ['realm', 'nonce', 'opaque', 'qop']), {
username: username,
uri: path,
algorithm: 'MD5',
response: _cryptoJs2.default.MD5(responseParams.join(':')).toString(_cryptoJs2.default.enc.Hex),
nc: nonce_count,
cnonce: cnonce
}));
var paramArray = keys(authParams).reduce(function (result, key) {
if (typeof authParams[key] !== 'function') {
result.push(key + '=' + quoteIfRelevant(authParams, key));
}
return result;
}, []);
return paramArray.join(',');
}
/**
* Exact same parameters as fetch
* @param {string} url
* @param {object} parameters
*/
function fetchWithDigest(url, parameters) {
var headers = parameters.headers,
method = parameters.method,
body = parameters.body,
username = parameters.username,
password = parameters.password;
return fetch(url, _extends({}, parameters)).then(function (initialResults) {
if (initialResults && initialResults.headers && initialResults.headers.get('www-authenticate')) {
var digestHeader = getDigestHeaderValue(initialResults.headers.get('www-authenticate'), { url: url, method: method, headers: headers, username: username, password: password });
return fetch(url, _extends({}, parameters, { headers: _extends({}, headers, { Authorization: 'Digest ' + digestHeader }) }));
}
return initialResults;
});
}
;