"use strict";

var _nodeForge = _interopRequireDefault(require("node-forge"));

var _path = _interopRequireDefault(require("path"));

var _https = _interopRequireDefault(require("https"));

var _http = _interopRequireDefault(require("http"));

var _net = _interopRequireDefault(require("net"));

var _isIp = _interopRequireDefault(require("is-ip"));

var _fs = _interopRequireDefault(require("fs"));

var _mkdirp = _interopRequireDefault(require("mkdirp"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var defaultDir = _path["default"].join(require('os').homedir(), '.spark-wallet', 'tls');

module.exports = function (app) {
  var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : app.settings.host;
  var dir = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultDir;
  var leEmail = arguments.length > 3 ? arguments[3] : undefined;

  var tlsOpt = leEmail ? letsencrypt(name, dir, leEmail) : selfsigned(name, dir),
      redir = function redir(req, res) {
    return res.writeHead(301, {
      Location: "https://".concat(req.headers.host || name, "/")
    }), res.end();
  },
      server = createMultiServer(tlsOpt, app, redir);

  tlsOpt.cert && app.get('/cert.pem', function (req, res) {
    return res.type('pem').send(tlsOpt.cert);
  }); // @TODO allow downloading letsencrypt's cert

  return new Promise(function (resolve) {
    return server.listen(app.settings.port, app.settings.host, function (_) {
      return resolve("https://".concat(app.settings.host, ":").concat(server.address().port));
    });
  });
}; // Self-signed certificate (no CA)


var selfsigned = function selfsigned(name, dir) {
  if (_fs["default"].existsSync(_path["default"].join(dir, 'key.pem'))) {
    var keyPem = _fs["default"].readFileSync(_path["default"].join(dir, 'key.pem')),
        certPem = _fs["default"].readFileSync(_path["default"].join(dir, 'cert.pem')),
        cert = _nodeForge["default"].pki.certificateFromPem(certPem),
        certDer = _nodeForge["default"].asn1.toDer(_nodeForge["default"].pki.certificateToAsn1(cert)).getBytes(),
        fprint = _nodeForge["default"].md.sha1.create().update(certDer).digest().toHex().match(/../g).join(':');

    console.log("Loaded TLS certificate with fingerprint ".concat(fprint, " from ").concat(dir));
    return {
      key: keyPem,
      cert: certPem
    };
  }

  var extensions = [].concat(defaultExt, [{
    name: 'subjectAltName',
    altNames: [(0, _isIp["default"])(name) ? {
      type: 7,
      ip: name
    } : {
      type: 2,
      value: name
    }]
  }]);

  var pems = require('selfsigned').generate([{
    name: 'commonName',
    value: name
  }], {
    extensions: extensions,
    keySize: 2048,
    algorithm: 'sha256'
  });

  !_fs["default"].existsSync(dir) && _mkdirp["default"].sync(dir);

  _fs["default"].writeFileSync(_path["default"].join(dir, 'key.pem'), pems["private"]);

  _fs["default"].writeFileSync(_path["default"].join(dir, 'cert.pem'), pems.cert);

  console.log("Created TLS certificate with fingerprint ".concat(pems.fingerprint, " in ").concat(dir));
  return {
    key: pems["private"],
    cert: pems.cert
  };
};

var defaultExt = [{
  name: 'basicConstraints',
  cA: true
}, {
  name: 'keyUsage',
  keyCertSign: true,
  digitalSignature: true,
  nonRepudiation: true,
  keyEncipherment: true,
  dataEncipherment: true
}]; // Automatic CA-signed TLS certificate registration via LetsEncrypt

var letsencrypt = function letsencrypt(name, dir, email) {
  console.log("Setting up LetsEncrypt CA-signed TLS cert for ".concat(name, " with email ").concat(email, " in ").concat(dir));

  var gl = require('greenlock').create({
    version: 'draft-12',
    server: 'https://acme-v02.api.letsencrypt.org/directory',
    configDir: _path["default"].join(dir, 'letsencrypt'),
    approveDomains: [name],
    email: email,
    agreeTos: true,
    debug: !!process.env.LE_DEBUG
  });

  if (!process.env.LE_NOVERIFY) {
    console.log('Starting LetsEncrypt verification server');

    var noCon = function noCon(req, res) {
      return res.writeHead(204), res.end();
    }; // 204 No Content


    _http["default"].createServer(gl.middleware(noCon)).listen(process.env.LE_PORT || 80).on('error', function (err) {
      console.error("ERROR: ".concat(err.code, " while starting vertification server on ").concat(err.address, ":").concat(err.port));
      console.error(err.stack || err);

      if (err.errno == 'EACCES') {
        console.error("\nYou don't have prmission to bind on ".concat(err.address, ":").concat(err.port, "."));
        console.error('See https://github.com/shesek/spark-wallet/blob/master/doc/tls.md#letsencrypt-integration for advice.');
      } else if (err.errno == 'EADDRINUSE') {
        console.error("\n".concat(err.address, ":").concat(err.port, " is already being used by some other program. Stop it and try again."));
      }

      process.exit(1);
    });
  }

  return gl.tlsOptions;
}; // Create a server capable of handling both TLS and plain HTTP requests
// This is done to redirect users accessing the TLS server without using 'https://'
// Protocol detection/delegation based on https://stackoverflow.com/a/42019773/865693


var createMultiServer = function createMultiServer(tlsOpt, tlsHandler, plainHandler) {
  var server = _net["default"].createServer(function (socket) {
    socket.once('error', function (err) {
      console.error('socket error:', err.stack || err);
      socket.destroy();
    });
    socket.once('data', function (buff) {
      socket.pause(); // Determine if this is a TLS or plain HTTP request

      var _byte = buff[0],
          proto = _byte === 22 ? 'https' : 32 < _byte && _byte < 127 ? 'http' : null;

      if (!proto) {
        console.error(new Error('Cannot detect HTTP/TLS request').stack);
        return socket.destroy();
      } // Push the buffer back onto the front of the data stream


      socket.unshift(buff); // Delegate the socket to the appropriate handler

      socket.server = server[proto];
      server[proto].emit('connection', socket);
      process.nextTick(function (_) {
        return socket.resume();
      });
    });
  });

  server.http = _http["default"].createServer(plainHandler);
  server.https = _https["default"].createServer(tlsOpt, tlsHandler);
  return server;
};