155 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			155 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| var HttpMiddleware = module.exports;
 | |
| var servernameRe = /^[a-z0-9\.\-]+$/i;
 | |
| var challengePrefix = "/.well-known/acme-challenge/";
 | |
| 
 | |
| HttpMiddleware.create = function(gl, defaultApp) {
 | |
|     if (defaultApp && "function" !== typeof defaultApp) {
 | |
|         throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})");
 | |
|     }
 | |
| 
 | |
|     return function(req, res, next) {
 | |
|         var hostname = HttpMiddleware.sanitizeHostname(req);
 | |
| 
 | |
|         req.on("error", function(err) {
 | |
|             explainError(gl, err, "http_01_middleware_socket", hostname);
 | |
|         });
 | |
| 
 | |
|         // Skip unless the path begins with /.well-known/acme-challenge/
 | |
|         if (!hostname || 0 !== req.url.indexOf(challengePrefix)) {
 | |
|             skipChallenge(req, res, next, defaultApp);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // HEADERS SENT DEBUG NOTE #2
 | |
|         // at this point, it's most likely Let's Encrypt server
 | |
|         // (or greenlock itself) performing the verification process
 | |
|         // Hmmm... perhaps we should change the greenlock prefix to test
 | |
|         // Anyway, we just got fast the first place where we could
 | |
|         // be sending headers.
 | |
| 
 | |
|         var token = req.url.slice(challengePrefix.length);
 | |
| 
 | |
|         var done = false;
 | |
|         var countA = 0;
 | |
|         var countB = 0;
 | |
|         gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token })
 | |
|             .catch(function(err) {
 | |
|                 countA += 1;
 | |
|                 // HEADERS SENT DEBUG NOTE #3
 | |
|                 // This is the second possible time we could be sending headers
 | |
|                 respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname);
 | |
|                 done = true;
 | |
|                 return { __done: true };
 | |
|             })
 | |
|             .then(function(result) {
 | |
|                 countB += 1;
 | |
|                 if (result && result.__done) {
 | |
|                     return;
 | |
|                 }
 | |
|                 if (done) {
 | |
|                     console.error("Sanity check fail: `done` is in a quantum state of both true and false... huh?");
 | |
|                     return;
 | |
|                 }
 | |
|                 // HEADERS SENT DEBUG NOTE #4b
 | |
|                 // This is the third/fourth possible time send headers
 | |
|                 return respondWithGrace(res, result, hostname, token);
 | |
|             })
 | |
|             .catch(function(err) {
 | |
|                 // HEADERS SENT DEBUG NOTE #5
 | |
|                 // I really don't see how this can be possible.
 | |
|                 // Every case appears to be accounted for
 | |
|                 console.error();
 | |
|                 console.error("[warning] Developer Error:" + (err.code || err.context || ""), countA, countB);
 | |
|                 console.error(err.stack);
 | |
|                 console.error();
 | |
|                 console.error(
 | |
|                     "This is probably the error that happens routinely on http2 connections, but we're not sure why."
 | |
|                 );
 | |
|                 console.error("To track the status or help contribute,");
 | |
|                 console.error("visit: https://git.rootprojects.org/root/greenlock-express.js/issues/9");
 | |
|                 console.error();
 | |
|                 try {
 | |
|                     res.end("Internal Server Error [1003]: See logs for details.");
 | |
|                 } catch (e) {
 | |
|                     // ignore
 | |
|                 }
 | |
|             });
 | |
|     };
 | |
| };
 | |
| 
 | |
| function skipChallenge(req, res, next, defaultApp) {
 | |
|     if ("function" === typeof defaultApp) {
 | |
|         defaultApp(req, res, next);
 | |
|     } else if ("function" === typeof next) {
 | |
|         next();
 | |
|     } else {
 | |
|         res.statusCode = 500;
 | |
|         res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)");
 | |
|     }
 | |
| }
 | |
| 
 | |
| function respondWithGrace(res, result, hostname, token) {
 | |
|     var keyAuth = result && result.keyAuthorization;
 | |
| 
 | |
|     // HEADERS SENT DEBUG NOTE #4b
 | |
|     // This is (still) the third/fourth possible time we could be sending headers
 | |
|     if (keyAuth && "string" === typeof keyAuth) {
 | |
|         res.setHeader("Content-Type", "text/plain; charset=utf-8");
 | |
|         res.end(keyAuth);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     res.statusCode = 404;
 | |
|     res.setHeader("Content-Type", "application/json; charset=utf-8");
 | |
|     res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } }));
 | |
| }
 | |
| 
 | |
| function explainError(gl, err, ctx, hostname) {
 | |
|     if (!err.servername) {
 | |
|         err.servername = hostname;
 | |
|     }
 | |
|     if (!err.context) {
 | |
|         err.context = ctx;
 | |
|     }
 | |
|     // leaving this in the build for now because it will help with existing error reports
 | |
|     console.error("[warning] network connection error:", (err.context || "") + " " + err.message);
 | |
|     (gl.notify || gl._notify)("error", err);
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| function respondToError(gl, res, err, ctx, hostname) {
 | |
|     // HEADERS SENT DEBUG NOTE #3b
 | |
|     // This is (still) the second possible time we could be sending headers
 | |
|     err = explainError(gl, err, ctx, hostname);
 | |
|     res.statusCode = 500;
 | |
|     res.end("Internal Server Error [1004]: See logs for details.");
 | |
| }
 | |
| 
 | |
| HttpMiddleware.getHostname = function(req) {
 | |
|     return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || "");
 | |
| };
 | |
| HttpMiddleware.sanitizeHostname = function(req) {
 | |
|     // we can trust XFH because spoofing causes no ham in this limited use-case scenario
 | |
|     // (and only telebit would be legitimately setting XFH)
 | |
|     var servername = HttpMiddleware.getHostname(req)
 | |
|         .toLowerCase()
 | |
|         .replace(/:.*/, "");
 | |
|     try {
 | |
|         req.hostname = servername;
 | |
|     } catch (e) {
 | |
|         // read-only express property
 | |
|     }
 | |
|     if (req.headers["x-forwarded-host"]) {
 | |
|         req.headers["x-forwarded-host"] = servername;
 | |
|     }
 | |
|     try {
 | |
|         req.headers.host = servername;
 | |
|     } catch (e) {
 | |
|         // TODO is this a possible error?
 | |
|     }
 | |
| 
 | |
|     return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || "";
 | |
| };
 |