Auth.prototype.onResponse = function (response) { var self = this , request = self.request if (!self.hasAuth || self.sentAuth) { return null } var c = caseless(response.headers) var authHeader = c.get('www-authenticate') var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase() // debug('reauth', authVerb) switch (authVerb) { case 'basic': return self.basic(self.user, self.pass, true) case 'bearer': return self.bearer(self.bearerToken, true) case 'jwt': return self.bearer(self.jwtToken, true) case 'digest': return self.digest(request.method, request.path, authHeader) } }
OAuth.prototype.onRequest = function (_oauth) { var self = this self.params = _oauth var uri = self.request.uri || {} , method = self.request.method || '' , headers = caseless(self.request.headers) , body = self.request.body || '' , qsLib = self.request.qsLib || qs var form , query , contentType = headers.get('content-type') || '' , formContentType = 'application/x-www-form-urlencoded' , transport = _oauth.transport_method || 'header' if (contentType.slice(0, formContentType.length) === formContentType) { contentType = formContentType form = body } if (uri.query) { query = uri.query } if (transport === 'body' && (method !== 'POST' || contentType !== formContentType)) { self.request.emit('error', new Error('oauth: transport_method of body requires POST ' + 'and content-type ' + formContentType)) } if (!form && typeof _oauth.body_hash === 'boolean') { _oauth.body_hash = self.buildBodyHash(_oauth, self.request.body.toString()) } var oa = self.buildParams(_oauth, uri, method, query, form, qsLib) switch (transport) { case 'header': self.request.setHeader('Authorization', 'OAuth ' + self.concatParams(oa, ',', '"')) break case 'query': var href = self.request.uri.href += (query ? '&' : '?') + self.concatParams(oa, '&') self.request.uri = url.parse(href) self.request.path = self.request.uri.path break case 'body': self.request.body = (form ? form + '&' : '') + self.concatParams(oa, '&') break default: self.request.emit('error', new Error('oauth: transport_method invalid')) } }
exports.oauth = function (args) { var uri = args.uri || {} , method = args.method || '' , headers = caseless(args.headers) , body = args.body || '' , _oauth = args.oauth || {} , qsLib = args.qsLib || qs var form , query , contentType = headers.get('content-type') || '' , formContentType = 'application/x-www-form-urlencoded' , transport = _oauth.transport_method || 'header' if (contentType.slice(0, formContentType.length) === formContentType) { contentType = formContentType form = body } if (uri.query) { query = uri.query } if (transport === 'body' && (method !== 'POST' || contentType !== formContentType)) { throw new Error('oauth: transport_method of \'body\' requires \'POST\' ' + 'and content-type \'' + formContentType + '\'') } var oa = this.buildParams(_oauth, uri, method, query, form, qsLib) var data switch (transport) { case 'header': data = 'OAuth ' + this.concatParams(oa, ',', '"') break case 'query': data = (query ? '&' : '?') + this.concatParams(oa, '&') break case 'body': data = (form ? form + '&' : '') + this.concatParams(oa, '&') break default: throw new Error('oauth: transport_method invalid') } return {oauth:data, transport:transport} }
module.exports = (message, expectedHeaders) => { let newMessage; if (message.includes('Missing required property:')) { const headerName = message.split('Missing required property: ')[1]; newMessage = `Header '${headerName}' is missing`; } else if (message.includes('No enum match for: ')) { const splitted = message.split('\' No enum match for: "'); const headerName = splitted[0].replace(/^At '\//, ''); const headerValue = splitted[1].replace(/"$/, ''); const expected = caseless(expectedHeaders).get(headerName); newMessage = `Header '${headerName}' has value '${headerValue}' instead of '${expected}'`; } else { throw new Error( "Unknown tv4 error message can't convert to headers message." ); } return newMessage; };
Request.prototype.onRequestResponse = function (response) { var self = this debug('onRequestResponse', self.uri.href, response.statusCode, response.headers) response.on('end', function() { debug('response end', self.uri.href, response.statusCode, response.headers) }) // The check on response.connection is a workaround for browserify. if (response.connection && response.connection.listeners('error').indexOf(connectionErrorHandler) === -1) { response.connection.setMaxListeners(0) response.connection.once('error', connectionErrorHandler) } if (self._aborted) { debug('aborted', self.uri.href) response.resume() return } if (self._paused) { response.pause() } else if (response.resume) { // response.resume should be defined, but check anyway before calling. Workaround for browserify. response.resume() } self.response = response response.request = self response.toJSON = responseToJSON // XXX This is different on 0.10, because SSL is strict by default if (self.httpModule === https && self.strictSSL && (!response.hasOwnProperty('client') || !response.client.authorized)) { debug('strict ssl error', self.uri.href) var sslErr = response.hasOwnProperty('client') ? response.client.authorizationError : self.uri.href + ' does not support SSL' self.emit('error', new Error('SSL Error: ' + sslErr)) return } // Save the original host before any redirect (if it changes, we need to // remove any authorization headers). Also remember the case of the header // name because lots of broken servers expect Host instead of host and we // want the caller to be able to specify this. self.originalHost = self.getHeader('host') if (!self.originalHostHeaderName) { self.originalHostHeaderName = self.hasHeader('host') } if (self.setHost) { self.removeHeader('host') } if (self.timeout && self.timeoutTimer) { clearTimeout(self.timeoutTimer) self.timeoutTimer = null } var targetCookieJar = (self._jar && self._jar.setCookie) ? self._jar : globalCookieJar var addCookie = function (cookie) { //set the cookie if it's domain in the href's domain. try { targetCookieJar.setCookie(cookie, self.uri.href, {ignoreError: true}) } catch (e) { self.emit('error', e) } } response.caseless = caseless(response.headers) if (response.caseless.has('set-cookie') && (!self._disableCookies)) { var headerName = response.caseless.has('set-cookie') if (Array.isArray(response.headers[headerName])) { response.headers[headerName].forEach(addCookie) } else { addCookie(response.headers[headerName]) } } var redirectTo = null if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) { var location = response.caseless.get('location') debug('redirect', location) if (self.followAllRedirects) { redirectTo = location } else if (self.followRedirects) { switch (self.method) { case 'PATCH': case 'PUT': case 'POST': case 'DELETE': // Do not follow redirects break default: redirectTo = location break } } } else if (response.statusCode === 401 && self._hasAuth && !self._sentAuth) { var authHeader = response.caseless.get('www-authenticate') var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase() debug('reauth', authVerb) switch (authVerb) { case 'basic': self.auth(self._user, self._pass, true) redirectTo = self.uri break case 'bearer': self.auth(null, null, true, self._bearer) redirectTo = self.uri break case 'digest': // TODO: More complete implementation of RFC 2617. // - check challenge.algorithm // - support algorithm="MD5-sess" // - handle challenge.domain // - support qop="auth-int" only // - handle Authentication-Info (not necessarily?) // - check challenge.stale (not necessarily?) // - increase nc (not necessarily?) // For reference: // http://tools.ietf.org/html/rfc2617#section-3 // https://github.com/bagder/curl/blob/master/lib/http_digest.c var challenge = {} var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi for (;;) { var match = re.exec(authHeader) if (!match) { break } challenge[match[1]] = match[2] || match[3] } var ha1 = md5(self._user + ':' + challenge.realm + ':' + self._pass) var ha2 = md5(self.method + ':' + self.uri.path) var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth' var nc = qop && '00000001' var cnonce = qop && uuid().replace(/-/g, '') var digestResponse = qop ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2) : md5(ha1 + ':' + challenge.nonce + ':' + ha2) var authValues = { username: self._user, realm: challenge.realm, nonce: challenge.nonce, uri: self.uri.path, qop: qop, response: digestResponse, nc: nc, cnonce: cnonce, algorithm: challenge.algorithm, opaque: challenge.opaque } authHeader = [] for (var k in authValues) { if (authValues[k]) { if (k === 'qop' || k === 'nc' || k === 'algorithm') { authHeader.push(k + '=' + authValues[k]) } else { authHeader.push(k + '="' + authValues[k] + '"') } } } authHeader = 'Digest ' + authHeader.join(', ') self.setHeader('authorization', authHeader) self._sentAuth = true redirectTo = self.uri break } } if (redirectTo && self.allowRedirect.call(self, response)) { debug('redirect to', redirectTo) // ignore any potential response body. it cannot possibly be useful // to us at this point. if (self._paused) { response.resume() } if (self._redirectsFollowed >= self.maxRedirects) { self.emit('error', new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + self.uri.href)) return } self._redirectsFollowed += 1 if (!isUrl.test(redirectTo)) { redirectTo = url.resolve(self.uri.href, redirectTo) } var uriPrev = self.uri self.uri = url.parse(redirectTo) // handle the case where we change protocol from https to http or vice versa if (self.uri.protocol !== uriPrev.protocol) { self._updateProtocol() } self.redirects.push( { statusCode : response.statusCode , redirectUri: redirectTo } ) if (self.followAllRedirects && response.statusCode !== 401 && response.statusCode !== 307) { self.method = 'GET' } // self.method = 'GET' // Force all redirects to use GET || commented out fixes #215 delete self.src delete self.req delete self.agent delete self._started if (response.statusCode !== 401 && response.statusCode !== 307) { // Remove parameters from the previous response, unless this is the second request // for a server that requires digest authentication. delete self.body delete self._form if (self.headers) { self.removeHeader('host') self.removeHeader('content-type') self.removeHeader('content-length') if (self.uri.hostname !== self.originalHost.split(':')[0]) { // Remove authorization if changing hostnames (but not if just // changing ports or protocols). This matches the behavior of curl: // https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710 self.removeHeader('authorization') } } } self.emit('redirect') self.init() return // Ignore the rest of the response } else { self._redirectsFollowed = self._redirectsFollowed || 0 // Be a good stream and emit end when the response is finished. // Hack to emit end on close because of a core bug that never fires end response.on('close', function () { if (!self._ended) { self.response.emit('end') } }) response.on('end', function () { self._ended = true }) var dataStream if (self.gzip) { var contentEncoding = response.headers['content-encoding'] || 'identity' contentEncoding = contentEncoding.trim().toLowerCase() if (contentEncoding === 'gzip') { dataStream = zlib.createGunzip() response.pipe(dataStream) } else { // Since previous versions didn't check for Content-Encoding header, // ignore any invalid values to preserve backwards-compatibility if (contentEncoding !== 'identity') { debug('ignoring unrecognized Content-Encoding ' + contentEncoding) } dataStream = response } } else { dataStream = response } if (self.encoding) { if (self.dests.length !== 0) { console.error('Ignoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.') } else if (dataStream.setEncoding) { dataStream.setEncoding(self.encoding) } else { // Should only occur on node pre-v0.9.4 (joyent/node@9b5abe5) with // zlib streams. // If/When support for 0.9.4 is dropped, this should be unnecessary. dataStream = dataStream.pipe(stringstream(self.encoding)) } } self.emit('response', response) self.dests.forEach(function (dest) { self.pipeDest(dest) }) dataStream.on('data', function (chunk) { self._destdata = true self.emit('data', chunk) }) dataStream.on('end', function (chunk) { self.emit('end', chunk) }) dataStream.on('error', function (error) { self.emit('error', error) }) dataStream.on('close', function () {self.emit('close')}) if (self.callback) { var buffer = bl() , strings = [] self.on('data', function (chunk) { if (Buffer.isBuffer(chunk)) { buffer.append(chunk) } else { strings.push(chunk) } }) self.on('end', function () { debug('end event', self.uri.href) if (self._aborted) { debug('aborted', self.uri.href) return } if (buffer.length) { debug('has body', self.uri.href, buffer.length) if (self.encoding === null) { // response.body = buffer // can't move to this until https://github.com/rvagg/bl/issues/13 response.body = buffer.slice() } else { response.body = buffer.toString(self.encoding) } } else if (strings.length) { // The UTF8 BOM [0xEF,0xBB,0xBF] is converted to [0xFE,0xFF] in the JS UTC16/UCS2 representation. // Strip this value out when the encoding is set to 'utf8', as upstream consumers won't expect it and it breaks JSON.parse(). if (self.encoding === 'utf8' && strings[0].length > 0 && strings[0][0] === '\uFEFF') { strings[0] = strings[0].substring(1) } response.body = strings.join('') } if (self._json) { try { response.body = JSON.parse(response.body) } catch (e) {} } debug('emitting complete', self.uri.href) if(typeof response.body === 'undefined' && !self._json) { response.body = '' } self.emit('complete', response, response.body) }) } //if no callback else{ self.on('end', function () { if (self._aborted) { debug('aborted', self.uri.href) return } self.emit('complete', response) }) } } debug('finish init function', self.uri.href) }