2019-06-03 09:47:07 +00:00
"use strict" ;
2016-08-10 17:10:00 +00:00
2018-06-29 09:24:58 +00:00
var PromiseA ;
try {
2019-06-03 09:47:07 +00:00
PromiseA = require ( "bluebird" ) ;
} catch ( e ) {
PromiseA = global . Promise ;
2018-06-29 09:24:58 +00:00
}
2016-08-12 07:02:33 +00:00
// opts.approveDomains(options, certs, cb)
2019-06-03 09:47:07 +00:00
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 ;
}
2016-08-16 01:15:16 +00:00
2019-06-03 09:47:07 +00:00
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 + "'" ) ;
}
2016-08-10 17:10:00 +00:00
2019-06-03 09:47:07 +00:00
function _createPlain ( plainPort ) {
if ( ! plainPort ) {
plainPort = 80 ;
}
2018-08-18 07:52:32 +00:00
2019-06-03 09:47:07 +00:00
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 ;
2018-08-17 02:45:31 +00:00
2019-06-03 09:47:07 +00:00
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" ) ;
}
2016-08-10 17:10:00 +00:00
2019-07-01 00:07:13 +00:00
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 ) ;
} ) ;
2019-06-03 09:47:07 +00:00
httpType = "http" ;
2018-12-22 14:37:16 +00:00
2019-06-03 09:47:07 +00:00
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 ] ;
}
}
2019-04-03 04:54:38 +00:00
2019-06-03 09:47:07 +00:00
if ( ! greenlock . servername ) {
resolve ( null ) ;
return ;
}
2019-04-03 04:54:38 +00:00
2019-06-03 09:47:07 +00:00
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 ) ;
}
} ) ;
} ) ;
}
} ;
}
2018-12-22 14:37:16 +00:00
2019-06-03 09:47:07 +00:00
function _create ( port ) {
if ( ! port ) {
port = 443 ;
}
2018-12-22 14:37:16 +00:00
2019-06-03 09:47:07 +00:00
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 ;
2018-08-18 07:52:32 +00:00
2019-06-03 09:47:07 +00:00
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" ) ;
}
2018-08-18 07:52:32 +00:00
2019-06-03 09:47:07 +00:00
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 ) ;
2019-04-03 04:54:38 +00:00
2019-06-03 09:47:07 +00:00
if ( ! context || server . _hasDefaultSecureContext ) {
return ;
}
if ( ! domain ) {
domain = greenlock . servername ;
}
if ( ! domain ) {
return ;
}
2019-04-03 04:54:38 +00:00
2019-06-03 09:47:07 +00:00
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)"
) ;
}
}
2019-07-01 00:07:13 +00:00
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 ) ;
2019-06-03 09:47:07 +00:00
try {
2019-07-01 00:07:13 +00:00
res . statusCode = 500 ;
res . end ( "Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler." ) ;
2019-06-03 09:47:07 +00:00
} catch ( e ) {
2019-07-01 00:07:13 +00:00
// ignore
// (headers may have already been sent, etc)
2019-06-03 09:47:07 +00:00
}
2019-07-01 00:07:13 +00:00
}
} ) ;
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 ) ;
} ) ;
2019-06-03 09:47:07 +00:00
server . type = httpType ;
2018-12-22 14:37:16 +00:00
2019-06-03 09:47:07 +00:00
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 ) ;
}
} ) ;
} ) ;
}
} ;
}
2016-08-11 07:07:20 +00:00
2019-06-03 09:47:07 +00:00
// NOTE: 'greenlock' is just 'opts' renamed
var greenlock = require ( "greenlock" ) . create ( opts ) ;
2016-08-10 17:10:00 +00:00
2019-06-03 09:47:07 +00:00
if ( ! opts . app ) {
opts . app = function ( req , res ) {
res . end ( "Hello, World!\nWith Love,\nGreenlock for Express.js" ) ;
} ;
}
2018-08-17 02:45:31 +00:00
2019-06-03 09:47:07 +00:00
opts . listen = function ( plainPort , port , fnPlain , fn ) {
var server ;
var plainServer ;
2018-08-18 02:43:32 +00:00
2019-06-03 09:47:07 +00:00
// 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 ;
}
2018-08-18 01:58:50 +00:00
2019-06-03 09:47:07 +00:00
var obj1 = _createPlain ( plainPort , true ) ;
var obj2 = _create ( port , false ) ;
2018-08-18 01:58:50 +00:00
2019-06-03 09:47:07 +00:00
plainServer = obj1 . server ;
server = obj2 . server ;
2018-08-18 02:43:32 +00:00
2019-06-03 09:47:07 +00:00
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
) ;
}
2018-08-18 07:52:32 +00:00
2019-06-03 09:47:07 +00:00
// 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 ;
2018-08-18 01:58:50 +00:00
2019-06-03 09:47:07 +00:00
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 ) ;
} ;
2016-08-16 01:15:16 +00:00
2019-06-03 09:47:07 +00:00
return greenlock ;
2016-08-10 17:10:00 +00:00
} ;