You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					185 lines
				
				4.4 KiB
			
		
		
			
		
	
	
					185 lines
				
				4.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								// Load modules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var Sntp = require('sntp');
							 | 
						||
| 
								 | 
							
								var Boom = require('boom');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Declare internals
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var internals = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.version = function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return require('../package.json').version;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.limits = {
							 | 
						||
| 
								 | 
							
								    maxMatchLength: 4096            // Limit the length of uris and headers to avoid a DoS attack on string matching
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Extract host and port from request
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								//                                            $1                            $2
							 | 
						||
| 
								 | 
							
								internals.hostHeaderRegex = /^(?:(?:\r\n)?\s)*((?:[^:]+)|(?:\[[^\]]+\]))(?::(\d+))?(?:(?:\r\n)?\s)*$/;              // (IPv4, hostname)|(IPv6)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.parseHost = function (req, hostHeaderName) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    hostHeaderName = (hostHeaderName ? hostHeaderName.toLowerCase() : 'host');
							 | 
						||
| 
								 | 
							
								    var hostHeader = req.headers[hostHeaderName];
							 | 
						||
| 
								 | 
							
								    if (!hostHeader) {
							 | 
						||
| 
								 | 
							
								        return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (hostHeader.length > exports.limits.maxMatchLength) {
							 | 
						||
| 
								 | 
							
								        return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var hostParts = hostHeader.match(internals.hostHeaderRegex);
							 | 
						||
| 
								 | 
							
								    if (!hostParts) {
							 | 
						||
| 
								 | 
							
								        return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return {
							 | 
						||
| 
								 | 
							
								        name: hostParts[1],
							 | 
						||
| 
								 | 
							
								        port: (hostParts[2] ? hostParts[2] : (req.connection && req.connection.encrypted ? 443 : 80))
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Parse Content-Type header content
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.parseContentType = function (header) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!header) {
							 | 
						||
| 
								 | 
							
								        return '';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return header.split(';')[0].trim().toLowerCase();
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Convert node's  to request configuration object
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.parseRequest = function (req, options) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!req.headers) {
							 | 
						||
| 
								 | 
							
								        return req;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Obtain host and port information
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var host;
							 | 
						||
| 
								 | 
							
								    if (!options.host ||
							 | 
						||
| 
								 | 
							
								        !options.port) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        host = exports.parseHost(req, options.hostHeaderName);
							 | 
						||
| 
								 | 
							
								        if (!host) {
							 | 
						||
| 
								 | 
							
								            return new Error('Invalid Host header');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var request = {
							 | 
						||
| 
								 | 
							
								        method: req.method,
							 | 
						||
| 
								 | 
							
								        url: req.url,
							 | 
						||
| 
								 | 
							
								        host: options.host || host.name,
							 | 
						||
| 
								 | 
							
								        port: options.port || host.port,
							 | 
						||
| 
								 | 
							
								        authorization: req.headers.authorization,
							 | 
						||
| 
								 | 
							
								        contentType: req.headers['content-type'] || ''
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return request;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.now = function (localtimeOffsetMsec) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return Sntp.now() + (localtimeOffsetMsec || 0);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.nowSecs = function (localtimeOffsetMsec) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return Math.floor(exports.now(localtimeOffsetMsec) / 1000);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.authHeaderRegex = /^(\w+)(?:\s+(.*))?$/;                                      // Header: scheme[ something]
							 | 
						||
| 
								 | 
							
								internals.attributeRegex = /^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/;   // !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Parse Hawk HTTP Authorization header
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.parseAuthorizationHeader = function (header, keys) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    keys = keys || ['id', 'ts', 'nonce', 'hash', 'ext', 'mac', 'app', 'dlg'];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!header) {
							 | 
						||
| 
								 | 
							
								        return Boom.unauthorized(null, 'Hawk');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (header.length > exports.limits.maxMatchLength) {
							 | 
						||
| 
								 | 
							
								        return Boom.badRequest('Header length too long');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var headerParts = header.match(internals.authHeaderRegex);
							 | 
						||
| 
								 | 
							
								    if (!headerParts) {
							 | 
						||
| 
								 | 
							
								        return Boom.badRequest('Invalid header syntax');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var scheme = headerParts[1];
							 | 
						||
| 
								 | 
							
								    if (scheme.toLowerCase() !== 'hawk') {
							 | 
						||
| 
								 | 
							
								        return Boom.unauthorized(null, 'Hawk');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var attributesString = headerParts[2];
							 | 
						||
| 
								 | 
							
								    if (!attributesString) {
							 | 
						||
| 
								 | 
							
								        return Boom.badRequest('Invalid header syntax');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var attributes = {};
							 | 
						||
| 
								 | 
							
								    var errorMessage = '';
							 | 
						||
| 
								 | 
							
								    var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Check valid attribute names
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (keys.indexOf($1) === -1) {
							 | 
						||
| 
								 | 
							
								            errorMessage = 'Unknown attribute: ' + $1;
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Allowed attribute value characters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ($2.match(internals.attributeRegex) === null) {
							 | 
						||
| 
								 | 
							
								            errorMessage = 'Bad attribute value: ' + $1;
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Check for duplicates
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (attributes.hasOwnProperty($1)) {
							 | 
						||
| 
								 | 
							
								            errorMessage = 'Duplicate attribute: ' + $1;
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        attributes[$1] = $2;
							 | 
						||
| 
								 | 
							
								        return '';
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (verify !== '') {
							 | 
						||
| 
								 | 
							
								        return Boom.badRequest(errorMessage || 'Bad header format');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return attributes;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.unauthorized = function (message, attributes) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return Boom.unauthorized(message, 'Hawk', attributes);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 |