2016-08-10 17:10:00 +00:00
'use strict' ;
2018-06-29 09:24:58 +00:00
var PromiseA ;
try {
PromiseA = require ( 'bluebird' ) ;
} catch ( e ) {
PromiseA = global . Promise ;
}
2016-08-12 07:02:33 +00:00
// opts.approveDomains(options, certs, cb)
2016-08-10 17:10:00 +00:00
module . exports . create = function ( opts ) {
2018-05-23 09:22:20 +00:00
// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
2018-08-17 02:45:31 +00:00
if ( ! opts . _communityPackage ) {
opts . _communityPackage = 'greenlock-express.js' ;
2018-11-05 18:11:30 +00:00
opts . _communityPackageVersion = require ( './package.json' ) . version ;
2018-08-17 02:45:31 +00:00
}
2016-08-16 01:15:16 +00:00
2018-08-17 02:45:31 +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 ;
2016-08-10 17:10:00 +00:00
}
2018-08-17 02:45:31 +00:00
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 ;
2016-08-16 01:15:16 +00:00
}
2018-08-17 02:45:31 +00:00
console . error ( e . code + ": '" + e . address + ":" + e . port + "'" ) ;
}
2016-08-10 17:10:00 +00:00
2018-12-22 14:37:16 +00:00
function _createPlain ( plainPort ) {
2018-08-17 02:45:31 +00:00
if ( ! plainPort ) { plainPort = 80 ; }
2018-08-18 07:52:32 +00:00
var parts = String ( plainPort ) . split ( ':' ) ;
var p = parts . pop ( ) ;
var addr = parts . join ( ':' ) . replace ( /^\[/ , '' ) . replace ( /\]$/ , '' ) ;
var args = [ ] ;
var httpType ;
var server ;
2018-08-17 02:45:31 +00:00
var validHttpPort = ( parseInt ( p , 10 ) >= 0 ) ;
2018-12-22 14:37:16 +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
}
2016-08-10 17:10:00 +00:00
2018-12-22 14:37:16 +00:00
server = require ( 'http' ) . createServer (
greenlock . middleware . sanitizeHost ( greenlock . middleware ( require ( 'redirect-https' ) ( ) ) )
) ;
httpType = 'http' ;
2018-12-23 19:43:23 +00:00
return { server : server , listen : function ( ) { return new PromiseA ( function ( resolve , reject ) {
2018-12-22 14:37:16 +00:00
args [ 0 ] = p ;
args . push ( function ( ) {
if ( ! greenlock . servername ) {
if ( Array . isArray ( greenlock . approvedDomains ) && greenlock . approvedDomains . length ) {
greenlock . servername = greenlock . approvedDomains [ 0 ] ;
2018-08-18 07:52:32 +00:00
}
2018-12-22 14:37:16 +00:00
if ( Array . isArray ( greenlock . approveDomains ) && greenlock . approvedDomains . length ) {
greenlock . servername = greenlock . approvedDomains [ 0 ] ;
}
}
2019-04-03 04:54:38 +00:00
2018-12-22 14:37:16 +00:00
if ( ! greenlock . servername ) {
resolve ( null ) ;
return ;
}
2019-04-03 04:54:38 +00:00
2018-12-22 14:37:16 +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 ) ;
}
} ) ;
} ) ; } } ;
}
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 ) ;
2018-08-18 07:52:32 +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-12-22 14:37:16 +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
if ( ! context || server . _hasDefaultSecureContext ) { return ; }
if ( ! domain ) { domain = greenlock . servername ; }
if ( ! domain ) { return ; }
2018-12-22 14:37:16 +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 ; }
2018-12-23 19:43:23 +00:00
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." ) ;
}
2018-12-22 14:37:16 +00:00
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);
} ) ;
} ) ;
} ;
2018-12-23 20:38:32 +00:00
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)" ) ;
}
}
2018-12-22 14:37:16 +00:00
server = https . createServer (
greenlock . tlsOptions
, 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 . type = httpType ;
return { server : server , listen : function ( ) { return new PromiseA ( function ( resolve ) {
2018-08-18 07:52:32 +00:00
args [ 0 ] = p ;
2018-12-22 14:37:16 +00:00
args . push ( function ( ) { resolve ( /*server*/ ) ; } ) ;
2018-08-18 07:52:32 +00:00
server . listen . apply ( server , args ) . on ( 'error' , function ( e ) {
2018-08-18 01:58:50 +00:00
if ( server . listenerCount ( 'error' ) < 2 ) {
2018-08-18 07:52:32 +00:00
console . warn ( "Did not successfully create http server and bind to port '" + p + "':" ) ;
2018-08-18 01:58:50 +00:00
explainError ( e ) ;
2018-08-18 07:52:32 +00:00
process . exit ( 41 ) ;
2018-08-18 01:58:50 +00:00
}
2018-08-17 02:45:31 +00:00
} ) ;
2018-12-22 14:37:16 +00:00
} ) ; } } ;
2018-08-17 02:45:31 +00:00
}
2016-08-11 07:07:20 +00:00
2018-08-22 22:36:33 +00:00
// NOTE: 'greenlock' is just 'opts' renamed
2018-08-17 02:45:31 +00:00
var greenlock = require ( 'greenlock' ) . create ( opts ) ;
2016-08-10 17:10:00 +00:00
2018-08-22 22:36:33 +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
2018-08-18 07:52:32 +00:00
opts . listen = function ( plainPort , port , fnPlain , fn ) {
2018-08-18 01:58:50 +00:00
var server ;
2018-08-18 02:43:32 +00:00
var plainServer ;
2018-11-05 06:58:50 +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)
2018-08-18 07:52:32 +00:00
if ( ! fn ) {
fn = fnPlain ;
fnPlain = null ;
2018-08-18 02:43:32 +00:00
}
2018-08-18 01:58:50 +00:00
2018-12-22 14:37:16 +00:00
var obj1 = _createPlain ( plainPort , true ) ;
var obj2 = _create ( port , false ) ;
2018-08-18 01:58:50 +00:00
2018-12-22 14:37:16 +00:00
plainServer = obj1 . server ;
server = obj2 . server ;
2018-08-18 02:43:32 +00:00
2018-12-22 14:37:16 +00:00
server . then = obj1 . listen ( ) . then ( function ( tlsOptions ) {
if ( tlsOptions ) {
2018-12-23 19:43:23 +00:00
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." ) ;
}
2018-12-22 14:37:16 +00:00
server . _hasDefaultSecureContext = true ;
2018-08-18 07:52:32 +00:00
}
2018-12-22 14:37:16 +00:00
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
2018-12-22 14:37:16 +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
2018-08-18 02:43:32 +00:00
server . unencrypted = plainServer ;
2018-08-18 01:58:50 +00:00
return server ;
2016-08-10 17:10:00 +00:00
} ;
2018-08-22 22:36:33 +00:00
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
2018-05-23 09:22:20 +00:00
return greenlock ;
2016-08-10 17:10:00 +00:00
} ;