mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2024-11-16 17:28:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			332 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| var PromiseA;
 | |
| try {
 | |
| 	PromiseA = require("bluebird");
 | |
| } catch (e) {
 | |
| 	PromiseA = global.Promise;
 | |
| }
 | |
| 
 | |
| // opts.approveDomains(options, certs, cb)
 | |
| module.exports.create = function(opts) {
 | |
| 	// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
 | |
| 	if (!opts._communityPackage) {
 | |
| 		opts._communityPackage = "greenlock-express.js";
 | |
| 		opts._communityPackageVersion = require("./package.json").version;
 | |
| 	}
 | |
| 
 | |
| 	function explainError(e) {
 | |
| 		console.error("Error:" + e.message);
 | |
| 		if ("EACCES" === e.errno) {
 | |
| 			console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'.");
 | |
| 			console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
 | |
| 			return;
 | |
| 		}
 | |
| 		if ("EADDRINUSE" === e.errno) {
 | |
| 			console.error("'" + e.address + ":" + e.port + "' is already being used by some other program.");
 | |
| 			console.error("You probably need to stop that program or restart your computer.");
 | |
| 			return;
 | |
| 		}
 | |
| 		console.error(e.code + ": '" + e.address + ":" + e.port + "'");
 | |
| 	}
 | |
| 
 | |
| 	function _createPlain(plainPort) {
 | |
| 		if (!plainPort) {
 | |
| 			plainPort = 80;
 | |
| 		}
 | |
| 
 | |
| 		var parts = String(plainPort).split(":");
 | |
| 		var p = parts.pop();
 | |
| 		var addr = parts
 | |
| 			.join(":")
 | |
| 			.replace(/^\[/, "")
 | |
| 			.replace(/\]$/, "");
 | |
| 		var args = [];
 | |
| 		var httpType;
 | |
| 		var server;
 | |
| 		var validHttpPort = parseInt(p, 10) >= 0;
 | |
| 
 | |
| 		if (addr) {
 | |
| 			args[1] = addr;
 | |
| 		}
 | |
| 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
 | |
| 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
 | |
| 		}
 | |
| 
 | |
| 		var mw = greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")()));
 | |
| 		server = require("http").createServer(function(req, res) {
 | |
| 			req.on("error", function(err) {
 | |
| 				console.error("Insecure Request Network Connection Error:");
 | |
| 				console.error(err);
 | |
| 			});
 | |
| 			mw(req, res);
 | |
| 		});
 | |
| 		httpType = "http";
 | |
| 
 | |
| 		return {
 | |
| 			server: server,
 | |
| 			listen: function() {
 | |
| 				return new PromiseA(function(resolve, reject) {
 | |
| 					args[0] = p;
 | |
| 					args.push(function() {
 | |
| 						if (!greenlock.servername) {
 | |
| 							if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
 | |
| 								greenlock.servername = greenlock.approvedDomains[0];
 | |
| 							}
 | |
| 							if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) {
 | |
| 								greenlock.servername = greenlock.approvedDomains[0];
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						if (!greenlock.servername) {
 | |
| 							resolve(null);
 | |
| 							return;
 | |
| 						}
 | |
| 
 | |
| 						return greenlock
 | |
| 							.check({ domains: [greenlock.servername] })
 | |
| 							.then(function(certs) {
 | |
| 								if (certs) {
 | |
| 									return {
 | |
| 										key: Buffer.from(certs.privkey, "ascii"),
 | |
| 										cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
 | |
| 									};
 | |
| 								}
 | |
| 								console.info(
 | |
| 									"Fetching certificate for '%s' to use as default for HTTPS server...",
 | |
| 									greenlock.servername
 | |
| 								);
 | |
| 								return new PromiseA(function(resolve, reject) {
 | |
| 									// using SNICallback because all options will be set
 | |
| 									greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) {
 | |
| 										if (err) {
 | |
| 											reject(err);
 | |
| 											return;
 | |
| 										}
 | |
| 										return greenlock
 | |
| 											.check({ domains: [greenlock.servername] })
 | |
| 											.then(function(certs) {
 | |
| 												resolve({
 | |
| 													key: Buffer.from(certs.privkey, "ascii"),
 | |
| 													cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
 | |
| 												});
 | |
| 											})
 | |
| 											.catch(reject);
 | |
| 									});
 | |
| 								});
 | |
| 							})
 | |
| 							.then(resolve)
 | |
| 							.catch(reject);
 | |
| 					});
 | |
| 					server.listen.apply(server, args).on("error", function(e) {
 | |
| 						if (server.listenerCount("error") < 2) {
 | |
| 							console.warn("Did not successfully create http server and bind to port '" + p + "':");
 | |
| 							explainError(e);
 | |
| 							process.exit(41);
 | |
| 						}
 | |
| 					});
 | |
| 				});
 | |
| 			}
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	function _create(port) {
 | |
| 		if (!port) {
 | |
| 			port = 443;
 | |
| 		}
 | |
| 
 | |
| 		var parts = String(port).split(":");
 | |
| 		var p = parts.pop();
 | |
| 		var addr = parts
 | |
| 			.join(":")
 | |
| 			.replace(/^\[/, "")
 | |
| 			.replace(/\]$/, "");
 | |
| 		var args = [];
 | |
| 		var httpType;
 | |
| 		var server;
 | |
| 		var validHttpPort = parseInt(p, 10) >= 0;
 | |
| 
 | |
| 		if (addr) {
 | |
| 			args[1] = addr;
 | |
| 		}
 | |
| 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
 | |
| 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
 | |
| 		}
 | |
| 
 | |
| 		var https;
 | |
| 		try {
 | |
| 			https = require("spdy");
 | |
| 			greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
 | |
| 			httpType = "http2 (spdy/h2)";
 | |
| 		} catch (e) {
 | |
| 			https = require("https");
 | |
| 			httpType = "https";
 | |
| 		}
 | |
| 		var sniCallback = greenlock.tlsOptions.SNICallback;
 | |
| 		greenlock.tlsOptions.SNICallback = function(domain, cb) {
 | |
| 			sniCallback(domain, function(err, context) {
 | |
| 				cb(err, context);
 | |
| 
 | |
| 				if (!context || server._hasDefaultSecureContext) {
 | |
| 					return;
 | |
| 				}
 | |
| 				if (!domain) {
 | |
| 					domain = greenlock.servername;
 | |
| 				}
 | |
| 				if (!domain) {
 | |
| 					return;
 | |
| 				}
 | |
| 
 | |
| 				return greenlock
 | |
| 					.check({ domains: [domain] })
 | |
| 					.then(function(certs) {
 | |
| 						// ignore the case that check doesn't have all the right args here
 | |
| 						// to get the same certs that it just got (eventually the right ones will come in)
 | |
| 						if (!certs) {
 | |
| 							return;
 | |
| 						}
 | |
| 						if (server.setSecureContext) {
 | |
| 							// only available in node v11.0+
 | |
| 							server.setSecureContext({
 | |
| 								key: Buffer.from(certs.privkey, "ascii"),
 | |
| 								cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
 | |
| 							});
 | |
| 							console.info("Using '%s' as default certificate", domain);
 | |
| 						} else {
 | |
| 							console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
 | |
| 						}
 | |
| 						server._hasDefaultSecureContext = true;
 | |
| 					})
 | |
| 					.catch(function(/*e*/) {
 | |
| 						// this may be that the test.example.com was requested, but it's listed
 | |
| 						// on the cert for demo.example.com which is in its own directory, not the other
 | |
| 						//console.warn("Unusual error: couldn't get newly authorized certificate:");
 | |
| 						//console.warn(e.message);
 | |
| 					});
 | |
| 			});
 | |
| 		};
 | |
| 		if (greenlock.tlsOptions.cert) {
 | |
| 			server._hasDefaultSecureContext = true;
 | |
| 			if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) {
 | |
| 				console.warn(
 | |
| 					"Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"
 | |
| 				);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		var mw = greenlock.middleware.sanitizeHost(function(req, res) {
 | |
| 			try {
 | |
| 				greenlock.app(req, res);
 | |
| 			} catch (e) {
 | |
| 				console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:");
 | |
| 				console.error(e);
 | |
| 				try {
 | |
| 					res.statusCode = 500;
 | |
| 					res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler.");
 | |
| 				} catch (e) {
 | |
| 					// ignore
 | |
| 					// (headers may have already been sent, etc)
 | |
| 				}
 | |
| 			}
 | |
| 		});
 | |
| 		server = https.createServer(greenlock.tlsOptions, function(req, res) {
 | |
| 			req.on("error", function(err) {
 | |
| 				console.error("HTTPS Request Network Connection Error:");
 | |
| 				console.error(err);
 | |
| 			});
 | |
| 			mw(req, res);
 | |
| 		});
 | |
| 		server.type = httpType;
 | |
| 
 | |
| 		return {
 | |
| 			server: server,
 | |
| 			listen: function() {
 | |
| 				return new PromiseA(function(resolve) {
 | |
| 					args[0] = p;
 | |
| 					args.push(function() {
 | |
| 						resolve(/*server*/);
 | |
| 					});
 | |
| 					server.listen.apply(server, args).on("error", function(e) {
 | |
| 						if (server.listenerCount("error") < 2) {
 | |
| 							console.warn("Did not successfully create http server and bind to port '" + p + "':");
 | |
| 							explainError(e);
 | |
| 							process.exit(41);
 | |
| 						}
 | |
| 					});
 | |
| 				});
 | |
| 			}
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	// NOTE: 'greenlock' is just 'opts' renamed
 | |
| 	var greenlock = require("greenlock").create(opts);
 | |
| 
 | |
| 	if (!opts.app) {
 | |
| 		opts.app = function(req, res) {
 | |
| 			res.end("Hello, World!\nWith Love,\nGreenlock for Express.js");
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	opts.listen = function(plainPort, port, fnPlain, fn) {
 | |
| 		var server;
 | |
| 		var plainServer;
 | |
| 
 | |
| 		// If there is only one handler for the `listening` (i.e. TCP bound) event
 | |
| 		// then we want to use it as HTTPS (backwards compat)
 | |
| 		if (!fn) {
 | |
| 			fn = fnPlain;
 | |
| 			fnPlain = null;
 | |
| 		}
 | |
| 
 | |
| 		var obj1 = _createPlain(plainPort, true);
 | |
| 		var obj2 = _create(port, false);
 | |
| 
 | |
| 		plainServer = obj1.server;
 | |
| 		server = obj2.server;
 | |
| 
 | |
| 		server.then = obj1.listen().then(function(tlsOptions) {
 | |
| 			if (tlsOptions) {
 | |
| 				if (server.setSecureContext) {
 | |
| 					// only available in node v11.0+
 | |
| 					server.setSecureContext(tlsOptions);
 | |
| 					console.info("Using '%s' as default certificate", greenlock.servername);
 | |
| 				} else {
 | |
| 					console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
 | |
| 				}
 | |
| 				server._hasDefaultSecureContext = true;
 | |
| 			}
 | |
| 			return obj2.listen().then(function() {
 | |
| 				// Report plain http status
 | |
| 				if ("function" === typeof fnPlain) {
 | |
| 					fnPlain.apply(plainServer);
 | |
| 				} else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) {
 | |
| 					console.info(
 | |
| 						"[:" +
 | |
| 							(plainServer.address().port || plainServer.address()) +
 | |
| 							"] Handling ACME challenges and redirecting to " +
 | |
| 							server.type
 | |
| 					);
 | |
| 				}
 | |
| 
 | |
| 				// Report h2/https status
 | |
| 				if ("function" === typeof fn) {
 | |
| 					fn.apply(server);
 | |
| 				} else if (!server.listenerCount("listening")) {
 | |
| 					console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type);
 | |
| 				}
 | |
| 			});
 | |
| 		}).then;
 | |
| 
 | |
| 		server.unencrypted = plainServer;
 | |
| 		return server;
 | |
| 	};
 | |
| 	opts.middleware.acme = function(opts) {
 | |
| 		return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts)));
 | |
| 	};
 | |
| 	opts.middleware.secure = function(app) {
 | |
| 		return greenlock.middleware.sanitizeHost(app);
 | |
| 	};
 | |
| 
 | |
| 	return greenlock;
 | |
| };
 |