"use strict";

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

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

var _assert = _interopRequireDefault(require("assert"));

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

var _basicAuth = _interopRequireDefault(require("basic-auth"));

var _nanoid = require("nanoid");

var _cookieParser = _interopRequireDefault(require("cookie-parser"));

var _crypto = require("crypto");

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

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

var cookieAge = 2592000000; // 1 month

var hmacStr = function hmacStr(key, data) {
  return (0, _crypto.createHmac)('sha256', key).update(data).digest('base64').replace(/\W+/g, '');
};

module.exports = function (app, cookieFile, login) {
  var username, password, accessKey;

  var hasFile = cookieFile && _fs["default"].existsSync(cookieFile);

  if (login) {
    // provided via --login (or LOGIN)
    ;

    var _login$split = login.split(':', 3);

    var _login$split2 = _slicedToArray(_login$split, 3);

    username = _login$split2[0];
    password = _login$split2[1];
    accessKey = _login$split2[2];
    (0, _assert["default"])(password, "Invalid login format, expecting \"username:pwd\"");
  } else if (hasFile) {
    console.log("Loading login credentials from ".concat(cookieFile));

    var _fs$readFileSync$toSt = _fs["default"].readFileSync(cookieFile).toString('utf-8').trim().split(':');

    var _fs$readFileSync$toSt2 = _slicedToArray(_fs$readFileSync$toSt, 3);

    username = _fs$readFileSync$toSt2[0];
    password = _fs$readFileSync$toSt2[1];
    accessKey = _fs$readFileSync$toSt2[2];
    (0, _assert["default"])(password, "Invalid login file at ".concat(cookieFile, ", expecting \"username:pwd[:access-key]\""));
  } else {
    // generate random
    username = (0, _nanoid.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
    password = (0, _nanoid.nanoid)(15);
    accessKey = hmacStr("".concat(username, ":").concat(password), 'access-key');
    console.log("No LOGIN or --login specified, picked username \"".concat(username, "\" with password \"").concat(password, "\""));

    if (cookieFile) {
      console.log("Writing login credentials to ".concat(cookieFile));
      !_fs["default"].existsSync(_path["default"].dirname(cookieFile)) && _mkdirp["default"].sync(_path["default"].dirname(cookieFile));

      _fs["default"].writeFileSync(cookieFile, [username, password, accessKey].join(':'));
    }
  } // HMAC derive the access key from the user/pwd combo, if not explicitly defined


  accessKey || (accessKey = hmacStr("".concat(username, ":").concat(password), 'access-key'));
  var manifestKey = hmacStr(accessKey, 'manifest-key').substr(0, 10),
      manifestRe = new RegExp("^/manifest-".concat(manifestKey, "/")),
      cookieKey = hmacStr(accessKey, 'cookie-key'),
      cookieOpt = {
    signed: true,
    httpOnly: true,
    sameSite: true,
    secure: app.enabled('tls'),
    maxAge: cookieAge
  };
  Object.assign(app.settings, {
    manifestKey: manifestKey,
    accessKey: accessKey
  });
  app.use((0, _cookieParser["default"])(cookieKey));
  return function (req, res, next) {
    // The manifest.json file has to be accessible without basic auth headers and without cookies.
    // This allow accessing the /manifest/ directory by including the `manifestKey` in the path.
    // Note that the manifestKey grants access _only_ to the manifest.
    if (req.method === 'GET' && manifestRe.test(req.url)) {
      req.url = req.url.replace(manifestRe, '/manifest/');
      return next();
    } // Browser pairing using access-key in query string


    if (req.method == 'GET' && req.path == '/' && req.query['access-key'] === accessKey) {
      res.set('Cache-Control', 'private, no-cache');
      res.cookie('user', username, cookieOpt); // issue a redirect to remove the access-key from the url and prevent it from being saved to the browser history.
      // the redirect is done to a page that issues a second <meta> redirect (below), to get the SameSite user cookie
      // sent properly when the pairing link is clicked on from a different site origin.

      return res.redirect(301, 'redir');
    }

    if (req.url == '/redir') return res.type('text/html').end('<meta http-equiv="refresh" content="0; url=.">'); // Authenticate using the access key token, via the X-Access header or using access-key on the body/query.
    // This also marks the request as csrfSafe. Used for RPC API calls and for SSE requests.

    if ((req.get('X-Access') || req.query['access-key'] || req.body['access-key']) === accessKey) {
      req.csrfSafe = true;
      return next();
    } // Authenticate with HMAC-signed cookies


    if (req.signedCookies.user) {
      return next();
    } // HTTP basic authentication (username/password)


    var cred = (0, _basicAuth["default"])(req);

    if (cred && cred.name === username && cred.pass === password) {
      // Once the user authenticates with basic auth, set a signed cookie to authenticate future requests.
      // HTTP basic auth is quirky, this makes for a smoother experience.
      res.cookie('user', username, cookieOpt);
      return next();
    }

    res.set('WWW-Authenticate', 'Basic realm="Private Area"').sendStatus(401);
  };
};