[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\"]\n}"
  },
  {
    "path": ".codeclimate.yml",
    "content": "engines:\n  csslint:\n    enabled: false\n  duplication:\n    enabled: true\n    config:\n      languages:\n      - javascript\n    exclude_fingerprints:\n      - faca6a53a5a4ae580279527e261dd1da\n      - 555c80119b06560cd2e96eb4e4e91f26\n      - a81ed3d8cb6b36bacb95a0ab40d09d78\n      - 9af23dc9a75d6c11f28122e9f83e7309\n      - faca6a53a5a4ae580279527e261dd1da\n      - fcc5dd7fd381cab75901402345e66511\n      - 144b552ca7471e150abb4ece59240e71\n      - 63a2186b2e759c9b5f6afb31965ab290\n      - 77a83a6a06048f2bda07f8c15c65c9ea\n  eslint:\n    enabled: true\n  fixme:\n    enabled: true\nratings:\n  paths:\n  - \"**.css\"\n  - \"**.js\"\nexclude_paths:\n- test/**/*\n- \"static/js/jquery.marquee.min.js\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "### Prerequisites\n\n* [ ] Can you reproduce the problem?\n* [ ] Did you read the entire [README.md](https://github.com/HR/Crypter/blob/master/readme.md)?\n* [ ] Did you read the [FAQs](https://github.com/HR/Crypter/blob/master/readme.md#faqs)?\n* [ ] Do you fully understand how Crypter works and how to use it?\n* [ ] Did you check the [issues](https://github.com/HR/Crypter/issues) to see if your bug or enhancement is already reported?\n\n### Description\n\n[Description of the bug or feature]\n\n### Steps to Reproduce\n\n1. [First Step]\n2. [Second Step]\n3. [and so on...]\n\n**Expected behavior:** [What you expected to happen]\n\n**Actual behavior:** [What actually happened]\n\n### Versions\n\nCrypter:\n\nRelease (full release name):\n\nOperating System (name and version):\n\nYou can find the full release name under https://github.com/HR/Crypter/releases. For version ```3.0.0``` and up, the version is found under ```Crypter > About``` or just in the menu ```Version X.Y.Z```.\n"
  },
  {
    "path": ".gitignore",
    "content": "### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# dev\ndest/\n.cred\ntest/specs\ntest/render.js\n.env\nspec/\ndeprecated/\nscreens/\n.crypting/\n.decrypting\n*.token\n\n\n# Icon must end with two\nIcon\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\napp/node_modules\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\n### Bower ###\nbower_components\n.bower-cache\n.bower-registry\n.bower-tmp\n\n# Other stuff to ignore\ndist/\n*.log\n.editorconfig\n.Debug\ncore/elements/*.js\ndebug\nbackups/\n*.app\nchromedriver*\n\n.nyc_output/\n"
  },
  {
    "path": ".nvmrc",
    "content": "12.14.1"
  },
  {
    "path": ".travis.yml",
    "content": "branches:\n  only:\n    - master\n\nmatrix:\n  include:\n    - os: linux\n      services: docker\n      sudo: required\n      dist: xenial\n      language: generic\n      env:\n        - ELECTRON_CACHE=$HOME/.cache/electron\n        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder\n    - os: osx\n      osx_image: xcode12\n      env:\n        - ELECTRON_CACHE=$HOME/.cache/electron\n        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder\n      language: node_js\n      node_js:\n\nbefore_script:\n - chmod +x ./script/*.sh\n - npm install npm -g\n\nscript: ./script/travis-build.sh\n\nafter_success:\n  # Cleanup\n  - git reset --hard HEAD\n  # Remove untracked and ignored files\n  - git clean -dfXn && git clean -dfX\n  - chmod +x ./script/*.sh\n  # Check if build triggered ([build] in commit message)\n  - lgcm=$(git log -1 --pretty=%B | xargs echo);\n    if [[ $lgcm == *\"[build]\"* ]]; then\n      ./script/build_ml.sh;\n    fi\n\n\nbefore_cache:\n  - rm -rf $HOME/.cache/electron-builder/wine\n\ncache:\n  yarn: true\n  apt: true\n  directories:\n    - node_modules\n    - $HOME/.cache/electron\n    - $HOME/.cache/electron-builder\n\n\nnotifications:\n  email:\n    on_success: never\n    on_failure: change\n\naddons:\n  apt:\n    sources:\n      - ubuntu-toolchain-r-test\n    packages:\n      - libgnome-keyring-dev\n      - icnsutils\n      - xvfb\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nThe following versions will receive security updates.\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 4.x     | :white_check_mark: |\n| < 4.0   | :x:                |\n\n## Reporting a Vulnerability\n\nPlease report a vulnerbility directly to me at h@rehman.email\n"
  },
  {
    "path": "app/config.js",
    "content": "'use strict'\n/**\n * config.js\n * Provides all essential config constants\n ******************************/\n\n// Fixed constants\nconst VIEWS_BASE_URI = `file://${__dirname}/static`\n\nmodule.exports = {\n  REPO: {\n    URL: 'https://github.com/HR/Crypter/',\n    RELEASES_API_URL: 'https://api.github.com/repos/HR/Crypter/releases/latest',\n    FORK: 'https://github.com/HR/Crypter/fork',\n    DOCS: 'https://github.com/HR/Crypter/blob/master/readme.md',\n    REPORT_ISSUE: 'https://github.com/HR/Crypter/issues/new'\n  },\n  WINDOW_OPTS: {\n    center: true,\n    show: true,\n    titleBarStyle: 'hiddenInset',\n    resizable: false,\n    maximizable: false,\n    movable: true,\n    webPreferences: {\n      nodeIntegration: true\n    }\n  },\n  VIEWS: {\n    BASE_URI: VIEWS_BASE_URI,\n    MASTERPASSPROMPT: `${VIEWS_BASE_URI}/masterpassprompt.html`,\n    SETUP: `${VIEWS_BASE_URI}/setup.html`,\n    CRYPTER: `${VIEWS_BASE_URI}/crypter.html`,\n    SETTINGS: `${VIEWS_BASE_URI}/settings.html`\n  },\n  CRYPTO: {\n    ENCRYPTION_TMP_DIR: '.crypting',\n    DECRYPTION_TMP_DIR: '.decrypting',\n    FILE_DATA: 'data',\n    FILE_CREDS: 'creds',\n    MASTERPASS_CREDS_FILE: 'credentials.crypter',\n    MASTERPASS_CREDS_PROPS: ['mpkhash', 'mpksalt', 'mpsalt'],\n    DECRYPT_OP: 'Decrypted',\n    DECRYPT_TITLE_PREPEND: 'Decrypted ',\n    ENCRYPT_OP: 'Encrypted',\n    EXT: '.crypto',\n    DEFAULTS: {\n      // Crypto default constants\n      ITERATIONS: 50000, // file encryption key derivation iterations\n      KEYLENGTH: 32, // encryption key length\n      IVLENGTH: 12, // initialisation vector length\n      ALGORITHM: 'aes-256-gcm', // encryption algorithm\n      DIGEST: 'sha256', // digest function\n      HASH_ALG: 'sha256', // hashing function\n      MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations\n    }\n  },\n  REGEX: {\n    APP_EVENT: /^app:[\\w-]+$/i,\n    ENCRYPTION_CREDS: /^Crypter(.*)$/gim,\n    MASTERPASS: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@!%*#?&]).{8,}$/\n  },\n  RESPONSES: {\n    invalid:\n      'MUST AT LEAST CONTAIN 1 UPPER & LOWER CASE ALPHABET, 1 NUMBER, 1 SYMBOL AND BE 8 CHARACTERS',\n    correct: 'CORRECT MASTERPASS',\n    incorrect: 'INCORRECT MASTERPASS',\n    setSuccess: 'MASTERPASS SUCCESSFULLY SET',\n    empty: 'PLEASE ENTER THE MASTERPASS',\n    resetSuccess:\n      \"You have successfully reset your MasterPass. You'll be redirected to verify it shortly.\",\n    exportSuccess: 'Successfully exported the credentials',\n    importSuccess:\n      'Successfully imported the credentials. You will need to verify the MasterPass for the credentials imported after Crypter relaunches.'\n  },\n  ERRORS: {\n    INVALID_MP_CREDS_FILE: 'Not a valid or corrupted Crypter credentials file!',\n    INVALID_FILE: 'Not a valid or corrupted CRYPTO file!',\n    AUTH_FAIL:\n      'Corrupted Crypter file or trying to decrypt on a different machine. See git.io/Crypter.info#faqs',\n    PROMISE: 'Oops, we encountered a problem...',\n    DECRYPT: 'Not a Crypter file (can not get salt, iv and authTag)',\n    MS: {\n      INVALID_FILE:\n        'Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?',\n      AUTH_FAIL: 'Unsupported state or unable to authenticate data'\n    }\n  },\n  COLORS: {\n    bad: '#dc3545',\n    good: '#28a745',\n    highlight: '#333333'\n  },\n  SETTINGS: {\n    RELAUNCH_TIMEOUT: 4000\n  }\n}\n"
  },
  {
    "path": "app/core/Db.js",
    "content": "'use strict'\n/**\n * Db.js\n * Custom JSON DB API implementation\n ******************************/\n\nconst _ = require('lodash')\nconst logger = require('electron-log')\nconst fs = require('fs-extra')\n\n/**\n * Constructor\n * An alias for the levelup constructor\n * @param {location}:string the location at which the Db to create or is at\n */\n\n// Db class constructor\nfunction Db (path, cb) {\n  // If Db already exists at the path, it opens the Db\n  // If no Db exists at the path, it creates a new Db\n\n  // Set db path\n  this.path = path\n  // Save context to instance\n  const self = this\n\n  // Create db if does not exist\n  fs.ensureFile(path, (err) => {\n    if (err) {\n      throw err\n      return\n    }\n    // file has now been created or exists\n    // either way, read the json now\n    fs.readFile(path, (err, db) => {\n      if (err) {\n        throw err\n        return\n      }\n      if (_.isEmpty(db)) {\n        logger.verbose('db is empty so initialising it...')\n        self._db = {}\n        self.open = true\n      } else {\n        try {\n          logger.verbose('db is not empty so json parsing it...')\n          // Get original JSON object or create new if does not exist\n          self._db = JSON.parse(db)\n          self.open = true\n        } catch (err) {\n          throw err\n          return\n        }\n      }\n      // call callback with newly created object\n      cb(self)\n    })\n  })\n}\n\n// Flush db to filesystem\nDb.prototype.flush = function () {\n  logger.verbose('flushing _db to fs')\n  const self = this\n  return new Promise((resolve, reject) => {\n    fs.outputJson(self.path, self._db, (err) => {\n      if (err) reject(err)\n      // successfully flushed db to disk\n      resolve()\n    })\n  })\n}\n\n// Add items to db\nDb.prototype.put = function (key, value) {\n  logger.verbose(`Putting ${key} in _db`)\n  const self = this\n  return new Promise((resolve, reject) => {\n    // set value with key in the internal db\n    self._db[key] = value\n    // then flush db to fs\n    self\n      .flush()\n      .then(() => {\n        resolve()\n      })\n      .catch((err) => {\n        reject(err)\n      })\n  })\n}\n\n// Get items from db\nDb.prototype.get = function (key) {\n  logger.verbose(`Getting ${key} from _db`)\n  const self = this\n  return new Promise((resolve, reject) => {\n    // Return the object if it exists otherwise return false\n    resolve(_.has(self._db, key) ? self._db[key] : false)\n  })\n}\n\nDb.prototype.close = function () {\n  logger.verbose(`Closing _db`)\n  const self = this\n  return new Promise((resolve, reject) => {\n    // Check if db is open\n    if (self.open) {\n      self\n        .flush()\n        .then(() => {\n          self.open = false\n          resolve()\n        })\n        .catch((err) => {\n          reject(err)\n        })\n    }\n  })\n}\n\n// save a global object of a name (objName)\nDb.prototype.saveGlobalObj = function (objName) {\n  logger.verbose(`Saving global obj ${objName} to _db`)\n  const self = this // get reference to the class instance\n  return new Promise((resolve, reject) => {\n    // If object not empty then save it in db\n    if (!_.isEmpty(global[objName])) {\n      // stringify object and store as a string with objName as key\n      try {\n        // wrap serialization of object around try catch as it could throw error\n        const serializedObj = JSON.stringify(global[objName])\n        self\n          .put(objName, serializedObj)\n          .then(() => {\n            resolve()\n          })\n          .catch((err) => {\n            reject(err) // db save error\n          })\n      } catch (err) {\n        reject(err)\n      }\n    } else {\n      // If object empty then do not save it\n      resolve()\n    }\n  })\n}\n\n// restores a global object of a name (objName)\nDb.prototype.restoreGlobalObj = function (objName) {\n  logger.verbose(`Restoring global obj ${objName} from _db`)\n  const self = this // get reference to th class instance\n  return new Promise((resolve, reject) => {\n    // get serialized object from db\n    self\n      .get(objName)\n      .then((serializedObj) => {\n        try {\n          // deserialize object and set as global\n          global[objName] = JSON.parse(serializedObj) // try parsing JSON\n          resolve()\n        } catch (err) {\n          // if error occurs while parsing, reject promise\n          reject(err)\n        }\n      })\n      .catch((err) => {\n        // I/O or other error, pass it up the callback\n        reject(err)\n      })\n  })\n}\n\nmodule.exports = Db\n"
  },
  {
    "path": "app/core/MasterPass.js",
    "content": "'use strict'\n/**\n * MasterPass.js\n * MasterPass functionality\n ******************************/\n\nconst crypto = require('./crypto'),\n  keytar = require('keytar'),\n  SERVICE = 'Crypter',\n  ACCOUNT = 'MasterPass'\n\n// TODO: Make independent from global obj! use param instead\n\nexports.init = async () => {\n  const masterpass = await keytar.getPassword(SERVICE, ACCOUNT)\n  if (!masterpass) return false\n  const mpk = await crypto.deriveKey(masterpass, global.creds.mpsalt)\n  return mpk\n}\n\nexports.save = masterpass => keytar.setPassword(SERVICE, ACCOUNT, masterpass)\n\n// Check MasterPass\nexports.check = masterpass => {\n  return new Promise((resolve, reject) => {\n    // deriveKey using the salt originally used to generate the\n    // MasterPassKey\n    crypto\n      .deriveKey(masterpass, global.creds.mpsalt)\n      .then(mpk => {\n        // generate the hash for the MasterPassKey\n        return crypto.genPassHash(mpk.key, global.creds.mpksalt)\n      })\n      .then(mpk => {\n        // check if MasterPassKey hash is equal to the MasterPassKey hash in mdb\n        // Use timingSafeEqual to protect against timing attacks\n        const match = crypto.timingSafeEqual(global.creds.mpkhash, mpk.hash)\n        // return the match and derived key\n        resolve({ match, key: mpk.key })\n      })\n      .catch(err => {\n        reject(err)\n      })\n  })\n}\n\n// Set MasterPass\nexports.set = masterpass => {\n  return new Promise((resolve, reject) => {\n    // Derive the MasterPassKey from the supplied masterpass\n    crypto\n      .deriveKey(masterpass, null)\n      .then(mp => exports.save(masterpass).then(() => mp))\n      .then(mp => {\n        // Save the salt used to generate the MasterPassKey\n        global.creds.mpsalt = mp.salt\n        // generate the hash for the MasterPassKey\n        return crypto.genPassHash(mp.key, null)\n      })\n      .then(mpk => {\n        // Save the salt used to generate the masterpass\n        global.creds.mpkhash = mpk.hash\n        global.creds.mpksalt = mpk.salt\n        // return the derived mpkey\n        resolve(mpk.key)\n      })\n      .catch(err => {\n        // reject if error occurs\n        reject(err)\n      })\n  })\n}\n"
  },
  {
    "path": "app/core/MasterPassKey.js",
    "content": "'use strict'\n/**\n * MasterPass.js\n * Provides a way to securely set and retrieve the MasterPass globally\n * MasterPassKey is protected (private var) and only exist in Main memory\n ******************************/\n\n // Uses closure to securely store MasterPassKey in MasterPassKey object\nconst MasterPassKey = (function () {\n  // Private mpk variable that stores the MasterPassKey\n  const mpk = new WeakMap()\n\n  // Class constructor\n  function MasterPassKey (key) {\n    // Initialise the new instance of class with the MasterPassKey\n    mpk.set(this, key)\n  }\n\n  // Public get method the for mpk\n  MasterPassKey.prototype.get = function () {\n    if (mpk.get(this) === undefined) {\n      // If MasterPassKey not set or delected\n      return new Error('MasterPassKey is not set')\n    } else {\n      // If MasterPassKey is set then return it\n      return mpk.get(this)\n    }\n  }\n\n  // Public set method the for mpk\n  MasterPassKey.prototype.set = function (key) {\n    if (key instanceof Buffer) {\n      // If the key is a Buffer then set it\n      mpk.set(this, key)\n    } else {\n      // If the key is not a Buffer return an erro\n      return new Error('MasterPassKey not a Buffer')\n    }\n  }\n\n  // Delete the MasterPassKey\n  MasterPassKey.prototype.delete = function (key) {\n    mpk.delete(this)\n  }\n\n  return MasterPassKey\n}())\n\nmodule.exports = MasterPassKey\n"
  },
  {
    "path": "app/core/crypto.js",
    "content": "'use strict'\n/**\n * crypto.js\n * Provides the crypto functionality required\n ******************************/\n\nconst fs = require('fs-extra')\nconst path = require('path')\nconst scrypto = require('crypto')\nconst logger = require('electron-log')\nconst Readable = require('stream').Readable\nconst tar = require('tar-fs')\nconst { CRYPTO, REGEX, ERRORS } = require('../config')\n\n// Helper functions\n\nlet readFile = path => {\n  return new Promise((resolve, reject) => {\n    fs.readFile(path, 'utf-8', (err, data) => {\n      if (err) reject(err)\n      resolve(data)\n    })\n  })\n}\n\n// Exports\n\nexports.crypt = (origpath, masterpass) => {\n  return new Promise((resolve, reject) => {\n    logger.verbose(`Encrypting ${origpath}...`)\n    // Resolve the destination path for encrypted file\n    exports\n      .encrypt(origpath, masterpass)\n      .then(creds => {\n        resolve({\n          op: CRYPTO.ENCRYPT_OP, // Crypter operation\n          name: path.basename(origpath), // filename\n          path: origpath, // path of the (unencrypted) file\n          cryptPath: creds.cryptpath, // path of the encrypted file\n          salt: creds.salt.toString('hex'), // salt used to derivekey in hex\n          key: creds.key.toString('hex'), // dervived key in hex\n          iv: creds.iv.toString('hex'), // iv in hex\n          authTag: creds.tag.toString('hex') // authTag in hex\n        })\n      })\n      .catch(err => {\n        reject(err)\n      })\n  })\n}\n\nexports.encrypt = (origpath, mpkey) => {\n  // Encrypts any arbitrary data passed with the pass\n  return new Promise((resolve, reject) => {\n    // derive the encryption key\n    exports\n      .deriveKey(mpkey, null, CRYPTO.DEFAULTS.ITERATIONS)\n      .then(dcreds => {\n        let tag\n        let isDirectory = fs.lstatSync(origpath).isDirectory()\n        let tempd = `${path.dirname(origpath)}/${CRYPTO.ENCRYPTION_TMP_DIR}`\n        let dataDestPath = `${tempd}/data`\n        let credsDestPath = `${tempd}/creds`\n        logger.verbose(\n          `tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}, isDirectory: ${isDirectory}`\n        )\n        // create tempd temporary directory\n        fs.mkdirs(tempd, err => {\n          if (err) reject(err)\n          logger.verbose(`Created ${tempd} successfully`)\n          // readstream to read the (unencrypted) file\n          const origin = isDirectory\n            ? tar.pack(origpath)\n            : fs.createReadStream(origpath)\n          // create data and creds file\n          const dataDest = fs.createWriteStream(dataDestPath)\n          const credsDest = fs.createWriteStream(credsDestPath)\n          // generate a cryptographically secure random iv\n          const iv = scrypto.randomBytes(CRYPTO.DEFAULTS.IVLENGTH)\n          // create the AES-256-GCM cipher with iv and derive encryption key\n          const cipher = scrypto.createCipheriv(\n            CRYPTO.DEFAULTS.ALGORITHM,\n            dcreds.key,\n            iv\n          )\n\n          // Read file, apply tranformation (encryption) to stream and\n          // then write stream to filesystem\n          origin\n            .on('error', err => reject(err))\n            .pipe(cipher)\n            .on('error', err => reject(err))\n            .pipe(dataDest)\n            .on('error', err => reject(err))\n            .on('finish', () => {\n              // get the generated Message Authentication Code\n              tag = cipher.getAuthTag()\n              // Write credentials used to encrypt in creds file\n              const creds = {\n                type: 'CRYPTO',\n                iv: iv.toString('hex'),\n                authTag: tag.toString('hex'),\n                salt: dcreds.salt.toString('hex'),\n                isDir: isDirectory\n              }\n              credsDest.end(JSON.stringify(creds))\n            })\n\n          // writestream finish handler\n          credsDest.on('finish', () => {\n            let tarDestPath = origpath + CRYPTO.EXT\n            const tarDest = fs.createWriteStream(tarDestPath)\n            const tarPack = tar.pack(tempd)\n            // Pack directory and zip into a .crypto file\n            tarPack\n              .on('error', err => reject(err))\n              .pipe(tarDest)\n              .on('error', err => reject(err))\n              .on('finish', () => {\n                // Remove temporary dir tempd\n                fs.remove(tempd, err => {\n                  if (err) reject(err)\n                  // return all the credentials and parameters used for encryption\n                  logger.verbose('Successfully deleted tempd!')\n                  resolve({\n                    salt: dcreds.salt,\n                    key: dcreds.key,\n                    cryptpath: tarDestPath,\n                    tag: tag,\n                    iv: iv\n                  })\n                })\n              })\n          })\n        })\n      })\n      .catch(err => reject(err))\n  })\n}\n\nexports.decrypt = (origpath, mpkey) => {\n  // Decrypts a crypto format file passed with the pass\n  return new Promise((resolve, reject) => {\n    logger.verbose(`Decrypting ${origpath}...`)\n    // Extract a directory\n    let tempd = `${path.dirname(origpath)}/${CRYPTO.DECRYPTION_TMP_DIR}`\n    let dataOrigPath = `${tempd}/${CRYPTO.FILE_DATA}`\n    let credsOrigPath = `${tempd}/${CRYPTO.FILE_CREDS}`\n    let dataDestPath = origpath.replace(CRYPTO.EXT, '')\n    dataDestPath = dataDestPath.replace(\n      path.basename(dataDestPath),\n      path.basename(dataDestPath)\n    )\n    let tarOrig = fs.createReadStream(origpath)\n    let tarExtr = tar.extract(tempd)\n    // Extract tar to CRYPTO.DECRYPTION_TMP_DIR directory\n    tarOrig\n      .on('error', err => reject(err))\n      .pipe(tarExtr)\n      .on('error', err => reject(err))\n      .on('finish', () => {\n        // Now read creds and use to decrypt data\n        logger.verbose('Finished extracting')\n\n        readFile(credsOrigPath)\n          .then(credsData => {\n            let creds\n            try {\n              // Try creds v2\n              creds = JSON.parse(credsData)\n              creds = [creds.iv, creds.authTag, creds.salt, creds.isDir]\n            } catch (error) {\n              // Try creds v1\n              let credsLine = credsData.trim().match(REGEX.ENCRYPTION_CREDS)\n              if (!credsLine) {\n                return reject(new Error(ERRORS.DECRYPT))\n              }\n              creds = credsLine[0].split('#').slice(1)\n            }\n\n            const iv = Buffer.from(creds[0], 'hex')\n            const authTag = Buffer.from(creds[1], 'hex')\n            const salt = Buffer.from(creds[2], 'hex')\n            const isDir = creds[3]\n\n            logger.verbose(\n              `Extracted data, iv: ${iv}, authTag: ${authTag}, salt: ${salt}`\n            )\n            // Read encrypted data stream\n            const dataOrig = fs.createReadStream(dataOrigPath)\n            // derive the original encryption key for the file\n            exports\n              .deriveKey(mpkey, salt, CRYPTO.DEFAULTS.ITERATIONS)\n              .then(dcreds => {\n                try {\n                  let decipher = scrypto.createDecipheriv(\n                    CRYPTO.DEFAULTS.ALGORITHM,\n                    dcreds.key,\n                    iv\n                  )\n                  decipher.setAuthTag(authTag)\n                  let dataDest = isDir\n                    ? tar.extract(dataDestPath)\n                    : fs.createWriteStream(dataDestPath)\n\n                  dataOrig\n                    .on('error', err => reject(err))\n                    .pipe(decipher)\n                    .on('error', err => reject(err))\n                    .pipe(dataDest)\n                    .on('error', err => reject(err))\n                    .on('finish', () => {\n                      logger.verbose(`Encrypted to ${dataDestPath}`)\n                      // Now delete tempd (temporary directory)\n                      fs.remove(tempd, err => {\n                        if (err) reject(err)\n                        logger.verbose(`Removed temp dir ${tempd}`)\n                        resolve({\n                          op: CRYPTO.DECRYPT_OP,\n                          name: path.basename(origpath),\n                          path: origpath,\n                          cryptPath: dataDestPath,\n                          salt: salt.toString('hex'),\n                          key: dcreds.key.toString('hex'),\n                          iv: iv.toString('hex'),\n                          authTag: authTag.toString('hex')\n                        })\n                      })\n                    })\n                } catch (err) {\n                  reject(err)\n                }\n              })\n          })\n          .catch(err => {\n            reject(err)\n          })\n      })\n  })\n}\n\nexports.deriveKey = (pass, psalt) => {\n  return new Promise((resolve, reject) => {\n    // reject with error if pass not provided\n    if (!pass) reject(new Error('Pass to derive key from not provided'))\n\n    // If psalt is provided and is a Buffer then assign it\n    // If psalt is provided and is not a Buffer then coerce it and assign it\n    // If psalt is not provided then generate a cryptographically secure salt\n    // and assign it\n    const salt = psalt\n      ? Buffer.isBuffer(psalt)\n        ? psalt\n        : Buffer.from(psalt)\n      : scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)\n\n    // derive the key using the salt, password and default crypto setup\n    scrypto.pbkdf2(\n      pass,\n      salt,\n      CRYPTO.DEFAULTS.MPK_ITERATIONS,\n      CRYPTO.DEFAULTS.KEYLENGTH,\n      CRYPTO.DEFAULTS.DIGEST,\n      (err, key) => {\n        if (err) reject(err)\n        // return the key and the salt\n        resolve({ key, salt })\n      }\n    )\n  })\n}\n\n// create a sha256 hash of the MasterPassKey\nexports.genPassHash = (masterpass, salt) => {\n  return new Promise((resolve, reject) => {\n    // convert the masterpass (of type Buffer) to a hex encoded string\n    // if it is not already one\n    const pass = Buffer.isBuffer(masterpass)\n      ? masterpass.toString('hex')\n      : masterpass\n\n    // if salt provided then the MasterPass is being checked\n    // if salt not provided then the MasterPass is being set\n    if (salt) {\n      // create hash from the contanation of the pass and salt\n      // assign the hex digest of the created hash\n      const hash = scrypto\n        .createHash(CRYPTO.DEFAULTS.HASH_ALG)\n        .update(`${pass}${salt}`)\n        .digest('hex')\n      resolve({ hash, key: masterpass })\n    } else {\n      // generate a cryptographically secure salt and use it as the salt\n      const salt = scrypto\n        .randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)\n        .toString('hex')\n      // create hash from the contanation of the pass and salt\n      // assign the hex digest of the created hash\n      const hash = scrypto\n        .createHash(CRYPTO.DEFAULTS.HASH_ALG)\n        .update(`${pass}${salt}`)\n        .digest('hex')\n      resolve({ hash, salt, key: masterpass })\n    }\n  })\n}\n\n// Converts a buffer array to a hex string\nexports.buf2hex = arr => {\n  const buf = Buffer.from(arr)\n  return buf.toString('hex')\n}\n\n// Compares vars in a constant time (protects against timing attacks)\nexports.timingSafeEqual = (a, b) => {\n  // convert args to buffers if not already\n  a = Buffer.isBuffer(a) ? a : Buffer.from(a)\n  b = Buffer.isBuffer(b) ? b : Buffer.from(b)\n  var result = 0\n  var l = a.length\n  while (l--) {\n    // bitwise comparison\n    result |= a[l] ^ b[l]\n  }\n  return result === 0\n}\n"
  },
  {
    "path": "app/index.js",
    "content": "'use strict'\n/**\n * index.js\n * Entry point for app execution\n ******************************/\n\nconst { app, dialog, BrowserWindow } = require('electron'),\n  { openNewGitHubIssue, debugInfo } = require('electron-util'),\n  debug = require('electron-debug'),\n  unhandled = require('electron-unhandled')\n\nunhandled({\n  reportButton: error => {\n    openNewGitHubIssue({\n      user: 'HR',\n      repo: 'Crypter',\n      body: `\\`\\`\\`\\n${error.stack}\\n\\`\\`\\`\\n\\n---\\n\\n${debugInfo()}`\n    })\n  }\n})\ndebug()\napp.setAppUserModelId('com.github.hr.crypter')\n\n// MasterPass credentials global\nglobal.creds = {}\n// User settings global\nglobal.settings = {}\n// Paths global (only resolved at runtime)\nglobal.paths = {\n  mdb: `${app.getPath('userData')}/mdb`,\n  userData: app.getPath('userData'),\n  home: app.getPath('home'),\n  documents: app.getPath('documents')\n}\n\nlet fileToCrypt\nlet settingsWindowNotOpen = true\n\nconst logger = require('electron-log')\nconst { existsSync } = require('fs-extra')\nconst { checkUpdate } = require('./utils/update')\n// Core\nconst Db = require('./core/Db')\nconst MasterPass = require('./core/MasterPass')\nconst MasterPassKey = require('./core/MasterPassKey')\n// Windows\nconst crypter = require('./src/crypter')\nconst masterPassPrompt = require('./src/masterPassPrompt')\nconst setup = require('./src/setup')\nconst settings = require('./src/settings')\nconst { ERRORS } = require('./config')\n\n// Debug info\nlogger.info(`Crypter v${app.getVersion()}`)\nlogger.info(`Process args: ${process.argv}`)\nlogger.info(`AppPath: ${app.getAppPath()}`)\nlogger.info(`UseData Path: ${app.getPath('userData')}`)\n// Change exec path\nprocess.chdir(app.getAppPath())\nlogger.info(`Changed cwd to: ${process.cwd()}`)\nlogger.info(`Electron v${process.versions.electron}`)\nlogger.info(`Electron node v${process.versions.node}`)\n// Prevent second instance\nconst gotTheLock = app.requestSingleInstanceLock()\nif (!gotTheLock) {\n  app.quit()\n}\n\n/**\n * Promisification of initialisation\n **/\n\nconst init = function () {\n  return new Promise(function (resolve, reject) {\n    // initialise mdb\n    global.mdb = new Db(global.paths.mdb, function (mdb) {\n      // Get the credentials serialized object from mdb\n      resolve(mdb.get('creds'))\n    })\n  })\n}\n\nconst initMain = function () {\n  logger.verbose(`initialising Main...`)\n  return global.mdb\n    .restoreGlobalObj('creds')\n    .then(() => MasterPass.init())\n    .then(mpk => mpk && (global.MasterPassKey = new MasterPassKey(mpk.key)))\n}\n\n/**\n * Event handlers\n **/\n\n// Main event handler\napp.on('ready', function () {\n  // Check for updates, silently\n  checkUpdate().catch(err => {\n    logger.warn(err)\n  })\n  // Check synchronously whether paths exist\n  init()\n    .then(mainRun => {\n      logger.info(`Init done.`)\n      // If the credentials not find in mdb, run setup\n      // otherwise run main\n      if (mainRun) {\n        // Run main\n        logger.info(`Main run. Creating CrypterWindow...`)\n\n        // Initialise (open mdb and get creds)\n        initMain()\n          .then(mpLoaded => {\n            logger.verbose(\n              'INIT: MasterPass',\n              mpLoaded ? 'loaded' : 'not saved'\n            )\n            // Obtain MasterPass, derive MasterPassKey and set globally\n            return mpLoaded || createWindow(masterPassPrompt, false)\n          })\n          .then(() => {\n            // Create the Crypter window and open it\n            return createWindow(crypter, fileToCrypt)\n          })\n          .then(() => {\n            // Quit app after crypterWindow is closed\n            app.quit()\n          })\n          .catch(function (error) {\n            if (error) {\n              // Catch any fatal errors and exit\n              logger.error(`PROMISE ERR: ${error.stack}`)\n              dialog.showErrorBox(ERRORS.PROMISE, error.message)\n            }\n            app.quit()\n          })\n      } else {\n        // Run Setup\n        logger.info('Setup run. Creating Setup wizard...')\n\n        createWindow(setup)\n          .then(() => {\n            logger.info('MAIN Setup successfully completed. quitting...')\n            // setup successfully completed\n            app.quit()\n          })\n          .catch(function (error) {\n            logger.error(`PROMISE ERR: ${error.stack}`)\n            // Display error to user\n            dialog.showErrorBox(ERRORS.PROMISE, error.message)\n            app.quit()\n          })\n      }\n    })\n    .catch(function (error) {\n      logger.error(`PROMISE ERR: ${error.stack}`)\n      // Display error to user\n      dialog.showErrorBox(ERRORS.PROMISE, error.message)\n      app.quit()\n    })\n})\n\n/**\n * Electron events\n **/\napp.on('will-finish-launching', () => {\n  // Check if launched with a file (opened with app in macOS)\n  app.on('open-file', (event, file) => {\n    if (app.isReady() === false) {\n      // Opening when not launched yet\n      logger.info('Launching with open-file ' + file)\n      fileToCrypt = file\n    }\n    event.preventDefault()\n  })\n\n  // Check if launched with a file (opened with app in Windows)\n  if (\n    process.argv[1] &&\n    process.argv[1].length > 1 &&\n    existsSync(process.argv[1])\n  ) {\n    fileToCrypt = process.argv[1]\n  }\n})\n\napp.on('window-all-closed', () => {\n  logger.verbose('APP: window-all-closed event emitted')\n  // On macOS it is common for applications and their menu bar\n  // to stay active until the user quits explicitly with Cmd + Q\n})\n\napp.on('quit', () => {\n  logger.info('APP: quit event emitted')\n  global.mdb.close().catch(err => {\n    console.error(err)\n    throw err\n  })\n})\n\napp.on('will-quit', event => {\n  // will exit program once exit procedures have been run\n  logger.info(`APP.ON('will-quit'): will-quit event emitted`)\n  global.mdb.close().catch(err => {\n    console.error(err)\n    throw err\n  })\n})\n\n/**\n * Custom events\n **/\n\napp.on('app:quit', () => {\n  logger.verbose('APP: app:quit event emitted')\n  app.quit()\n})\n\napp.on('app:open-settings', () => {\n  logger.verbose('APP: app:open-settings event emitted')\n  createWindow(settings)\n})\n\napp.on('app:check-update', () => {\n  logger.verbose('APP: app:check-updates event emitted')\n  // Check for updates\n  checkUpdate()\n    .then(updateAvailable => {\n      if (!updateAvailable) {\n        dialog.showMessageBox({\n          type: 'info',\n          message: 'No update available.',\n          detail: `You have the latest version Crypter ${app.getVersion()} :)`\n        })\n      }\n    })\n    .catch(err => {\n      logger.warn(err)\n      dialog.showErrorBox(\n        'Failed to check for update',\n        `An error occured while checking for update:\\n ${err.message}`\n      )\n    })\n})\n\napp.on('app:relaunch', () => {\n  logger.verbose('APP: app:relaunch event emitted')\n  // Relaunch Crypter\n  app.relaunch()\n  // Exit successfully\n  app.quit(0)\n  // app.exit(0)\n})\n\napp.on('app:reset-masterpass', () => {\n  logger.verbose('APP: app:reset-masterpass event emitted')\n  createWindow(masterPassPrompt)\n})\n\n/**\n * Promisification of windows\n **/\n\nfunction createWindow (window, ...args) {\n  const winInst = BrowserWindow.getAllWindows().find(\n    win => win.getTitle() === window.title\n  )\n  // Focus on existing instance\n  if (winInst) return winInst.focus()\n  return new Promise((resolve, reject) =>\n    window.window(global, ...args, err => {\n      if (err) reject(err)\n      resolve()\n    })\n  )\n}\n"
  },
  {
    "path": "app/package.json",
    "content": "{\n  \"name\": \"Crypter\",\n  \"productName\": \"Crypter\",\n  \"version\": \"5.0.0\",\n  \"description\": \"An innovative, convenient and secure cross-platform crypto app\",\n  \"license\": \"MIT\",\n  \"repository\": \"https://github.com/HR/Crypter\",\n  \"homepage\": \"https://github.com/HR/Crypter\",\n  \"bugs\": \"https://github.com/HR/Crypter/issues\",\n  \"private\": true,\n  \"main\": \"index.js\",\n  \"author\": {\n    \"name\": \"Habib Rehman\",\n    \"email\": \"H@Rehman.email\",\n    \"url\": \"https://git.io/HR\"\n  },\n  \"dependencies\": {\n    \"electron-debug\": \"^3.1.0\",\n    \"electron-log\": \"^4.2.2\",\n    \"electron-unhandled\": \"^3.0.2\",\n    \"electron-util\": \"^0.14.2\",\n    \"estraverse\": \"5.1.0\",\n    \"fs-extra\": \"9.0.1\",\n    \"handlebars\": \"4.7.6\",\n    \"jquery\": \"3.5.1\",\n    \"jquery.marquee\": \"1.5.0\",\n    \"keytar\": \"^6.0.1\",\n    \"lodash\": \"4.17.19\",\n    \"moment\": \"2.27.0\",\n    \"normalize.css\": \"8.0.1\",\n    \"tar-fs\": \"2.1.0\"\n  }\n}\n"
  },
  {
    "path": "app/src/crypter.js",
    "content": "const { app, ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, ERRORS, WINDOW_OPTS } = require('../config')\nconst crypto = require('../core/crypto')\nconst menuTemplate = require('./mainMenu')\nconst { isCryptoFile } = require('../utils/utils')\nconst logger = require('electron-log')\nconst title = 'Crypter'\n\nexports.title = title\n\nexports.window = function (global, fileToCrypt, callback) {\n  // creates a new BrowserWindow\n  let win = new BrowserWindow({\n    width: 350,\n    height: 460,\n    title,\n    ...WINDOW_OPTS\n  })\n  // create menu from menuTemplate and set\n  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))\n  // loads crypt.html view into the BrowserWindow\n  win.loadURL(VIEWS.CRYPTER)\n  let webContents = win.webContents\n\n  webContents.once('did-finish-load', () => {\n    // Process any file opened with app before this window\n    if (fileToCrypt) {\n      logger.info('Got a file to crypt', fileToCrypt)\n      cryptFile(fileToCrypt)\n    }\n  })\n\n\n  function encrypt(filePath) {\n    // Update UI\n    webContents.send('encryptingFile', filePath)\n    crypto.crypt(filePath, global.MasterPassKey.get())\n      .then((file) => {\n        webContents.send('cryptedFile', file)\n      })\n      .catch((err) => {\n        logger.info(`cryptFile error`)\n        logger.error(err)\n        webContents.send('cryptErr', err.message)\n      })\n  }\n\n  function decrypt(filePath) {\n    // Update UI\n    webContents.send('decryptingFile', filePath)\n    crypto.decrypt(filePath, global.MasterPassKey.get())\n      .then((file) => {\n        logger.info('decrypted')\n        webContents.send('decryptedFile', file)\n      })\n      .catch((err) => {\n        logger.info(`decryptFile error`)\n        logger.error(err)\n        switch (err.message.trim()) {\n        case ERRORS.MS.INVALID_FILE:\n          webContents.send('cryptErr', ERRORS.INVALID_FILE)\n          break;\n        case ERRORS.MS.AUTH_FAIL:\n          webContents.send('cryptErr', ERRORS.AUTH_FAIL)\n          break;\n        default:\n          webContents.send('cryptErr', err.message)\n        }\n      })\n  }\n\n  function cryptFile(file) {\n    if (isCryptoFile(file)) {\n      decrypt(file)\n    } else {\n      encrypt(file)\n    }\n  }\n\n  ipcMain.on('app:open-settings', (event) => {\n    logger.verbose('CRYPTER: app:open-settings emitted.')\n    app.emit('app:open-settings')\n  })\n\n  // Process any file opened with app while this window is active\n  ipcMain.on('cryptFile', (event, file) => cryptFile(file))\n\n  app.on('open-file', (event, file) => {\n    if (app.isReady()) {\n      // Opening when already launched\n      logger.info('Opening file ' + file)\n      cryptFile(file)\n    }\n    event.preventDefault()\n  })\n\n  win.on('closed', function () {\n    logger.info('win.closed event emitted for PromptWindow')\n    win = null\n    callback()\n  })\n\n  return win\n}"
  },
  {
    "path": "app/src/mainMenu.js",
    "content": "const {app, shell} = require('electron')\nconst menu = require('./menu')\n\nif (process.platform === 'darwin') {\n  menu.unshift({\n    label: 'Crypter',\n    submenu: [\n      { label: 'About Crypter', role: 'about' },\n      { label: `Version ${app.getVersion()}`, enabled: false },\n      { label: 'Check for Update', click() { app.emit('app:check-update') } },\n      { type: 'separator' },\n      { label: 'Preferences…', click() { app.emit('app:open-settings') } },\n      { type: 'separator' },\n      { label: 'Quit', click() { app.emit('app:quit') } }\n    ]\n  })\n}\n\nmodule.exports = menu\n"
  },
  {
    "path": "app/src/masterPassPrompt.js",
    "content": "const { ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, WINDOW_OPTS } = require('../config')\nconst MasterPass = require('../core/MasterPass')\nconst MasterPassKey = require('../core/MasterPassKey')\nconst logger = require('electron-log')\nconst menuTemplate = require('./menu')\nconst title = 'MasterPass'\n\nexports.title = title\n\nexports.window = function (global, resetOnly, callback) {\n  let noMP = true // init noMP flag with false\n  let error = null\n  const CLOSE_TIMEOUT = 2000\n  \n  // creates a new BrowserWindow\n  let win = new BrowserWindow({\n    width: 300,\n    height: 460,\n    title,\n    ...WINDOW_OPTS\n  })\n  // create menu from menuTemplate and set\n  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))\n  const qs = resetOnly ? '?reset=true' : ''\n  // loads masterpassprompt.html view into the BrowserWindow\n  win.loadURL(VIEWS.MASTERPASSPROMPT + qs)\n\n  let webContents = win.webContents\n\n  ipcMain.on('checkMasterPass', function (event, masterpass) {\n    logger.verbose('IPCMAIN: checkMasterPass emitted. Checking MasterPass...')\n    // Check user submitted MasterPass\n    MasterPass.check(masterpass)\n      .then(res => {\n        if (res.match) {\n          // Password matches\n          logger.info('IPCMAIN: PASSWORD MATCHES!')\n          // Save MasterPassKey (while program is running)\n          global.MasterPassKey = new MasterPassKey(res.key)\n          // Save for next time\n          MasterPass.save(masterpass)\n          // send result match result to masterpassprompt.html\n          webContents.send('checkMasterPassResult', {\n            err: null,\n            match: res.match\n          })\n          noMP = false\n          // Close after 1 second\n          setTimeout(function () {\n            // close window (invokes 'closed') event\n            win.close()\n          }, CLOSE_TIMEOUT)\n        } else {\n          logger.warn('IPCMAIN: PASSWORD DOES NOT MATCH!')\n          webContents.send('checkMasterPassResult', {\n            err: null,\n            match: res.match\n          })\n        }\n      })\n      .catch(err => {\n        // Inform user of error (on render side)\n        webContents.send('checkMasterPassResult', err.message)\n        // set error\n        error = err\n        // Close after 1 second\n        setTimeout(function () {\n          // close window (invokes 'closed') event\n          win.close()\n        }, CLOSE_TIMEOUT)\n      })\n  })\n\n  ipcMain.on('setMasterPass', function (event, masterpass) {\n    // setMasterPass event triggered by render proces\n    logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')\n    // derive MasterPassKey, genPassHash and set creds globally\n    MasterPass.set(masterpass)\n      .then(mpkey => {\n        // set the derived MasterPassKey globally\n        global.MasterPassKey = new MasterPassKey(mpkey)\n        return\n      })\n      .then(() => {\n        // save the credentials used to derive the MasterPassKey\n        return global.mdb.saveGlobalObj('creds')\n      })\n      .then(() => {\n        // Inform user that the MasterPass has successfully been set\n        logger.verbose('IPCMAIN: Masterpass has been reset successfully')\n        webContents.send('setMasterPassResult', null)\n      })\n      .catch(err => {\n        // Inform user of the error that occured while setting the MasterPass\n        webContents.send('setMasterPassResult', err.message)\n        error = err\n      })\n  })\n\n  win.on('closed', function () {\n    logger.info('win.closed event emitted for PromptWindow')\n    // send error and noMP back to callee (masterPassPromptWindow Promise)\n    if (callback) callback(error || noMP)\n    // close window by setting it to nothing (null)\n    win = null\n  })\n\n  return win\n}\n"
  },
  {
    "path": "app/src/menu.js",
    "content": "const {app, shell} = require('electron')\nconst {REPO} = require('../config')\n\nmodule.exports = [\n  {\n    label: 'Edit',\n    submenu: [\n      {\n        role: 'undo'\n      },\n      {\n        role: 'redo'\n      },\n      {\n        type: 'separator'\n      },\n      {\n        role: 'cut'\n      },\n      {\n        role: 'copy'\n      },\n      {\n        role: 'paste'\n      },\n      {\n        role: 'pasteandmatchstyle'\n      },\n      {\n        role: 'delete'\n      },\n      {\n        role: 'selectall'\n      }\n    ]\n  },\n  {\n    label: 'Help',\n    role: 'help',\n    submenu: [\n      { label: 'Documentation', click() { shell.openExternal(REPO.DOCS)} },\n      { type: 'separator' },\n      { label: 'Report Issue', click() { shell.openExternal(REPO.REPORT_ISSUE)} },\n      { label: 'Star Crypter', click() { shell.openExternal(REPO.URL)} },\n      { label: 'Contribute', click() { shell.openExternal(REPO.FORK)} },\n    ]\n  }\n]\n"
  },
  {
    "path": "app/src/settings.js",
    "content": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst menuTemplate = require('./menu')\nconst {CRYPTO, VIEWS, SETTINGS, ERRORS, WINDOW_OPTS} = require('../config')\nconst logger = require('electron-log')\nconst fs = require('fs-extra')\nconst title = 'Settings'\n\nexports.title = title\nexports.window = function (global, callback) {\n  // creates a new BrowserWindow\n  let win = new BrowserWindow({\n    width: 600,\n    height: 460,\n    title,\n    ...WINDOW_OPTS\n  })\n  // create menu from menuTemplate and set\n  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))\n\n  let webContents = win.webContents\n  // loads settings.html view into the BrowserWindow\n  win.loadURL(VIEWS.SETTINGS)\n\n  ipcMain.on('export', (event, dir) => {\n    logger.verbose(`SETTINGS: export event emitted, got ${dir}`)\n    const file = `${dir}/${CRYPTO.MASTERPASS_CREDS_FILE}`\n    fs.outputJson(file, global.creds, function (err) {\n      if (err) {\n        logger.error(err)\n        webContents.send('exportResult', err.message)\n      } else {\n        // Successfully exported\n        webContents.send('exportResult', null)\n      }\n    })\n  })\n\n  ipcMain.on('import', (event, file) => {\n    logger.verbose(`SETTINGS: import event emitted, got ${file}`)\n    fs.readJson(file, global.creds, function (err, credsObj) {\n      const invalidCredsErr = new Error(ERRORS.INVALID_MP_CREDS_FILE)\n      if (err) {\n        logger.error(err)\n        webContents.send('importResult', err.message)\n      } else {\n        let isCredsObjProp = function (prop) {\n          return credsObj.hasOwnProperty(prop)\n        }\n        let credsFileValid = CRYPTO.MASTERPASS_CREDS_PROPS.every(isCredsObjProp)\n        logger.verbose(`SETTINGS: Got for credsFileValid ${credsFileValid}`)\n\n        if (credsFileValid) {\n          // Is valid (i.e. has required properties)\n          // Save in-memory - global creds obj\n          global.creds = credsObj\n          // Save to fs - via global mdb\n          global.mdb.saveGlobalObj('creds')\n          // Successfully imported\n          webContents.send('importResult', null)\n          // Restart after timeout\n          setTimeout(function () {\n            app.emit('app:relaunch')\n          }, SETTINGS.RELAUNCH_TIMEOUT);\n        } else {\n          webContents.send('importResult', invalidCredsErr.message)\n        }\n\n      }\n    })\n  })\n\n  win.on('closed', function () {\n    logger.info('win.closed event emitted for SettingsWindow')\n    win = null\n    callback()\n  })\n\n  return win\n}\n"
  },
  {
    "path": "app/src/setup.js",
    "content": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst {VIEWS, WINDOW_OPTS} = require('../config')\nconst MasterPass = require('../core/MasterPass')\nconst MasterPassKey = require('../core/MasterPassKey')\nconst logger = require('electron-log')\nconst menuTemplate = require('./menu')\nconst title = 'Setup'\n\nexports.title = title\n\nexports.window = function (global, callback) {\n  // setup view controller\n\n  // creates the setup window\n  let win = new BrowserWindow({\n    width: 600,\n    height: 420,\n    title,\n    ...WINDOW_OPTS\n  })\n\n  // create menu from menuTemplate and set\n  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))\n\n  let webContents = win.webContents\n  let error\n  // loads setup.html view into the SetupWindow\n  win.loadURL(VIEWS.SETUP)\n\n  ipcMain.on('setMasterPass', function (event, masterpass) {\n    // setMasterPass event triggered by render proces\n    logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')\n    // derive MasterPassKey, genPassHash and set creds globally\n    MasterPass.set(masterpass)\n      .then((mpkey) => {\n        // set the derived MasterPassKey globally\n        global.MasterPassKey = new MasterPassKey(mpkey)\n        return\n      })\n      .then(() => {\n        // save the credentials used to derive the MasterPassKey\n        return global.mdb.saveGlobalObj('creds')\n      })\n      .then(() => {\n        // Inform user that the MasterPass has successfully been set\n        webContents.send('setMasterPassResult', null)\n      })\n      .catch((err) => {\n        // Inform user of the error that occured while setting the MasterPass\n        logger.error(err)\n        webContents.send('setMasterPassResult', err.message)\n        error = err\n      })\n  })\n\n  ipcMain.on('done', function (event, masterpass) {\n    // Dond event emotted from render process\n    logger.info('IPCMAIN: done emitted setup complete. Closing...')\n    // Setup successfully finished\n    // therefore set error to nothing\n    error = null\n    // Relaunch Crypter\n    app.emit('app:relaunch')\n  })\n\n  win.on('closed', function () {\n    logger.verbose('IPCMAIN: win.closed event emitted for setupWindow.')\n    // close window by setting it to nothing (null)\n    win = null\n    // if error occured then send error back to callee else send null\n    callback((error) ? error : null)\n  })\n}"
  },
  {
    "path": "app/static/crypter.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"styles/crypter.css\" />\n    <script src=\"js/common.js\" type=\"text/javascript\"></script>\n    <script src=\"js/crypter.js\" type=\"text/javascript\"></script>\n  </head>\n  <body>\n    <section id=\"crypt\">\n      <div class=\"panel-container\">\n        <div id=\"panel-crypt\" class=\"current\">\n          <a class=\"navigationLink\" data-action=\"app:open-settings\">\n            <img class=\"info\" src=\"images/icons/settings.svg\" alt=\"Settings\" />\n          </a>\n          <header>\n            <img src=\"images/icons/Crypter.svg\" alt=\"Crypter\" class=\"header\" />\n            <h1 class=\"title\">Crypter</h1>\n            <p class=\"subtitle\">Encrypt. Decrypt. Anything.</p>\n            <p class=\"leadinfo\">\n              Encrypt unlimited bits. Remember only a bit.\n            </p>\n            <p id=\"errLabel\" style=\"display: none;\"></p>\n          </header>\n          <div id=\"fileInput\" class=\"fancy\">\n            <p id=\"fileInputText\">\n              Select or Drop\n            </p>\n          </div>\n        </div>\n        <div id=\"panel-crypted\">\n          <div id=\"crypted-container\">\n            <!-- Crypted panel dynamically loaded here -->\n          </div>\n          <p class=\"intrfo\">\n            Note these details down<br />\n            (for use with third-party apps)\n          </p>\n          <footer>\n            <a class=\"back navigationLink\" data-panel=\"crypt\">\n              <img src=\"images/icons/back.svg\" alt=\"back\" />\n            </a>\n          </footer>\n        </div>\n      </div>\n    </section>\n    <script id=\"crypted-template\" type=\"text/x-handlebars-template\">\n      <header>\n        <img src=\"images/icons/{{op}}.svg\" class='header' />\n      </header>\n      <div id='finfo'>\n        <h3>\n          <div title=\"{{cryptPath}}\">\n            {{op}} {{name}}\n          </div>\n        </h3>\n        <table>\n          <tr>\n            <td>\n              Original file\n            </td>\n            <td class='file-path'>\n              <input type='text' readonly='readonly' value=\"{{path}}\" title=\"{{path}}\" />\n              <a href='#' onclick=\"showFile('{{path}}')\">\n                <img src='images/icons/eye.svg' />\n              </a>\n            </td>\n          </tr>\n          <tr>\n            <td>\n              {{op}} file\n            </td>\n            <td class='file-path'>\n              <input type='text' readonly='readonly' value=\"{{cryptPath}}\" title=\"{{cryptPath}}\" />\n              <a href='#' onclick=\"showFile('{{cryptPath}}')\">\n                <img src='images/icons/eye.svg' />\n              </a>\n            </td>\n          </tr>\n          <tr>\n            <td>\n              Initialisation Vector\n            </td>\n            <td>\n              <input type='text' readonly='readonly' value=\"{{iv}}\" title=\"{{iv}}\" />\n            </td>\n          </tr>\n          <tr>\n            <td>\n              Auth Tag\n            </td>\n            <td>\n              <input type='text' readonly='readonly' value=\"{{authTag}}\" title=\"{{authTag}}\" />\n            </td>\n          </tr>\n          <tr>\n            <td>\n              Salt\n            </td>\n            <td>\n              <input type='text' readonly='readonly' value=\"{{salt}}\" title=\"{{salt}}\" />\n            </td>\n          </tr>\n          <tr>\n            <td>\n              Encryption Key\n            </td>\n            <td>\n              <input type='text' readonly='readonly' value=\"{{key}}\" title=\"{{key}}\" />\n            </td>\n          </tr>\n        </table>\n      </div>\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "app/static/fonts/LICENSE.txt",
    "content": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n                        http://www.apache.org/licenses/\r\n\r\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n   1. Definitions.\r\n\r\n      \"License\" shall mean the terms and conditions for use, reproduction,\r\n      and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n      \"Licensor\" shall mean the copyright owner or entity authorized by\r\n      the copyright owner that is granting the License.\r\n\r\n      \"Legal Entity\" shall mean the union of the acting entity and all\r\n      other entities that control, are controlled by, or are under common\r\n      control with that entity. For the purposes of this definition,\r\n      \"control\" means (i) the power, direct or indirect, to cause the\r\n      direction or management of such entity, whether by contract or\r\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n      outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n      exercising permissions granted by this License.\r\n\r\n      \"Source\" form shall mean the preferred form for making modifications,\r\n      including but not limited to software source code, documentation\r\n      source, and configuration files.\r\n\r\n      \"Object\" form shall mean any form resulting from mechanical\r\n      transformation or translation of a Source form, including but\r\n      not limited to compiled object code, generated documentation,\r\n      and conversions to other media types.\r\n\r\n      \"Work\" shall mean the work of authorship, whether in Source or\r\n      Object form, made available under the License, as indicated by a\r\n      copyright notice that is included in or attached to the work\r\n      (an example is provided in the Appendix below).\r\n\r\n      \"Derivative Works\" shall mean any work, whether in Source or Object\r\n      form, that is based on (or derived from) the Work and for which the\r\n      editorial revisions, annotations, elaborations, or other modifications\r\n      represent, as a whole, an original work of authorship. For the purposes\r\n      of this License, Derivative Works shall not include works that remain\r\n      separable from, or merely link (or bind by name) to the interfaces of,\r\n      the Work and Derivative Works thereof.\r\n\r\n      \"Contribution\" shall mean any work of authorship, including\r\n      the original version of the Work and any modifications or additions\r\n      to that Work or Derivative Works thereof, that is intentionally\r\n      submitted to Licensor for inclusion in the Work by the copyright owner\r\n      or by an individual or Legal Entity authorized to submit on behalf of\r\n      the copyright owner. For the purposes of this definition, \"submitted\"\r\n      means any form of electronic, verbal, or written communication sent\r\n      to the Licensor or its representatives, including but not limited to\r\n      communication on electronic mailing lists, source code control systems,\r\n      and issue tracking systems that are managed by, or on behalf of, the\r\n      Licensor for the purpose of discussing and improving the Work, but\r\n      excluding communication that is conspicuously marked or otherwise\r\n      designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n      on behalf of whom a Contribution has been received by Licensor and\r\n      subsequently incorporated within the Work.\r\n\r\n   2. Grant of Copyright License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      copyright license to reproduce, prepare Derivative Works of,\r\n      publicly display, publicly perform, sublicense, and distribute the\r\n      Work and such Derivative Works in Source or Object form.\r\n\r\n   3. Grant of Patent License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      (except as stated in this section) patent license to make, have made,\r\n      use, offer to sell, sell, import, and otherwise transfer the Work,\r\n      where such license applies only to those patent claims licensable\r\n      by such Contributor that are necessarily infringed by their\r\n      Contribution(s) alone or by combination of their Contribution(s)\r\n      with the Work to which such Contribution(s) was submitted. If You\r\n      institute patent litigation against any entity (including a\r\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n      or a Contribution incorporated within the Work constitutes direct\r\n      or contributory patent infringement, then any patent licenses\r\n      granted to You under this License for that Work shall terminate\r\n      as of the date such litigation is filed.\r\n\r\n   4. Redistribution. You may reproduce and distribute copies of the\r\n      Work or Derivative Works thereof in any medium, with or without\r\n      modifications, and in Source or Object form, provided that You\r\n      meet the following conditions:\r\n\r\n      (a) You must give any other recipients of the Work or\r\n          Derivative Works a copy of this License; and\r\n\r\n      (b) You must cause any modified files to carry prominent notices\r\n          stating that You changed the files; and\r\n\r\n      (c) You must retain, in the Source form of any Derivative Works\r\n          that You distribute, all copyright, patent, trademark, and\r\n          attribution notices from the Source form of the Work,\r\n          excluding those notices that do not pertain to any part of\r\n          the Derivative Works; and\r\n\r\n      (d) If the Work includes a \"NOTICE\" text file as part of its\r\n          distribution, then any Derivative Works that You distribute must\r\n          include a readable copy of the attribution notices contained\r\n          within such NOTICE file, excluding those notices that do not\r\n          pertain to any part of the Derivative Works, in at least one\r\n          of the following places: within a NOTICE text file distributed\r\n          as part of the Derivative Works; within the Source form or\r\n          documentation, if provided along with the Derivative Works; or,\r\n          within a display generated by the Derivative Works, if and\r\n          wherever such third-party notices normally appear. The contents\r\n          of the NOTICE file are for informational purposes only and\r\n          do not modify the License. You may add Your own attribution\r\n          notices within Derivative Works that You distribute, alongside\r\n          or as an addendum to the NOTICE text from the Work, provided\r\n          that such additional attribution notices cannot be construed\r\n          as modifying the License.\r\n\r\n      You may add Your own copyright statement to Your modifications and\r\n      may provide additional or different license terms and conditions\r\n      for use, reproduction, or distribution of Your modifications, or\r\n      for any such Derivative Works as a whole, provided Your use,\r\n      reproduction, and distribution of the Work otherwise complies with\r\n      the conditions stated in this License.\r\n\r\n   5. Submission of Contributions. Unless You explicitly state otherwise,\r\n      any Contribution intentionally submitted for inclusion in the Work\r\n      by You to the Licensor shall be under the terms and conditions of\r\n      this License, without any additional terms or conditions.\r\n      Notwithstanding the above, nothing herein shall supersede or modify\r\n      the terms of any separate license agreement you may have executed\r\n      with Licensor regarding such Contributions.\r\n\r\n   6. Trademarks. This License does not grant permission to use the trade\r\n      names, trademarks, service marks, or product names of the Licensor,\r\n      except as required for reasonable and customary use in describing the\r\n      origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n   7. Disclaimer of Warranty. Unless required by applicable law or\r\n      agreed to in writing, Licensor provides the Work (and each\r\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n      implied, including, without limitation, any warranties or conditions\r\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n      PARTICULAR PURPOSE. You are solely responsible for determining the\r\n      appropriateness of using or redistributing the Work and assume any\r\n      risks associated with Your exercise of permissions under this License.\r\n\r\n   8. Limitation of Liability. In no event and under no legal theory,\r\n      whether in tort (including negligence), contract, or otherwise,\r\n      unless required by applicable law (such as deliberate and grossly\r\n      negligent acts) or agreed to in writing, shall any Contributor be\r\n      liable to You for damages, including any direct, indirect, special,\r\n      incidental, or consequential damages of any character arising as a\r\n      result of this License or out of the use or inability to use the\r\n      Work (including but not limited to damages for loss of goodwill,\r\n      work stoppage, computer failure or malfunction, or any and all\r\n      other commercial damages or losses), even if such Contributor\r\n      has been advised of the possibility of such damages.\r\n\r\n   9. Accepting Warranty or Additional Liability. While redistributing\r\n      the Work or Derivative Works thereof, You may choose to offer,\r\n      and charge a fee for, acceptance of support, warranty, indemnity,\r\n      or other liability obligations and/or rights consistent with this\r\n      License. However, in accepting such obligations, You may act only\r\n      on Your own behalf and on Your sole responsibility, not on behalf\r\n      of any other Contributor, and only if You agree to indemnify,\r\n      defend, and hold each Contributor harmless for any liability\r\n      incurred by, or claims asserted against, such Contributor by reason\r\n      of your accepting any such warranty or additional liability.\r\n\r\n   END OF TERMS AND CONDITIONS\r\n\r\n   APPENDIX: How to apply the Apache License to your work.\r\n\r\n      To apply the Apache License to your work, attach the following\r\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\r\n      replaced with your own identifying information. (Don't include\r\n      the brackets!)  The text should be enclosed in the appropriate\r\n      comment syntax for the file format. We also recommend that a\r\n      file or class name and description of purpose be included on the\r\n      same \"printed page\" as the copyright notice for easier\r\n      identification within third-party archives.\r\n\r\n   Copyright [yyyy] [name of copyright owner]\r\n\r\n   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n   you may not use this file except in compliance with the License.\r\n   You may obtain a copy of the License at\r\n\r\n       http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n   Unless required by applicable law or agreed to in writing, software\r\n   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n   See the License for the specific language governing permissions and\r\n   limitations under the License.\r\n"
  },
  {
    "path": "app/static/js/common.js",
    "content": "'use strict'\n/**\n * common.js\n * Contains all the functionality that is common between the views\n ******************************/\n\n/* Common variables */\n\n// Load JQuery library and make accessible via $\nwindow.$ = window.jQuery = require('jquery')\n// Cross-view dependencies\nconst {ipcRenderer, remote, shell} = require('electron')\nconst { app } = remote\nconst logger = require('electron-log')\nconst {REGEX, RESPONSES, COLORS} = require('../config')\nconst Handlebars = require('handlebars')\n\n/* Shared functions */\nfunction getUrlParameter(name) {\n  name = name.replace(/[\\[]/, '\\\\[').replace(/[\\]]/, '\\\\]');\n  var regex = new RegExp('[\\\\?&]' + name + '=([^&#]*)');\n  var results = regex.exec(location.search);\n  return results === null ? '' : decodeURIComponent(results[1].replace(/\\+/g, ' '));\n};\n\nfunction navigate (panel) {\n  let oldSel = $('.panel-container > div.current') // get current panel\n  let sel = $(`#panel-${panel}`) // get panel to navigate to\n  oldSel.removeClass('current') // apply hide styling\n  sel.addClass('current') // apply show styling\n}\n\nfunction validateMasterPass(field, errLabel) {\n  const MPel = $(`input#${field}Input`)\n  const masterpass = MPel.val()\n  if (!masterpass) {\n    // MP is empty\n    // set errLabel text to response for empty and show errLabel\n    errLabel.text(RESPONSES.empty).show()\n    // Clear MP input field\n    MPel.val('')\n  } else if (REGEX.MASTERPASS.test(masterpass)) {\n    // MP is valid\n    // Hide errLabel\n    errLabel.hide()\n    // Send valid MasterPass to controller function to be checked\n    ipcRenderer.send(field, masterpass)\n  } else {\n    // set errLabel text to response for invalid and show errLabel\n    errLabel.text(RESPONSES.invalid).show()\n    // Clear MP input field\n    MPel.val('')\n  }\n}\n\n/* Onload */\n$(window).on('load', function() {\n  $(\".navigationLink\").each(function(index) {\n    let $this = $(this)\n    $this.on('click', function(event) {\n      let target = $this.data(\"target\")\n      let panel = $this.data(\"panel\")\n      let tab = $this.data(\"tab\")\n      let action = $this.data(\"action\")\n\n      if (action) {\n        // Is an action to perform\n        if (REGEX.APP_EVENT.test(action)) {\n          console.log(`Got main app event ${action}`)\n          // Emit event on app\n          app.emit(action)\n        } else {\n          console.log(`Got render event ${action}`)\n          // Emit event in render proc\n          ipcRenderer.emit(action)\n        }\n      } else if (tab) {\n        console.log(`Got tab ${tab}`)\n        // is a tab to navigate to\n        $(\".item.active\").first().removeClass(\"active\")\n        $(`a[data-tab='${tab}']`).addClass(\"active\")\n        navigate(tab)\n      } else if (target) {\n        console.log(`Got URL ${target}`)\n        // is a URL so open it\n        shell.openExternal(target)\n      } else if (panel) {\n        console.log(`Got panel ${panel}`)\n        // target is just a panel to navigate to\n        navigate(panel)\n      }\n      return false\n    })\n  })\n})\n"
  },
  {
    "path": "app/static/js/crypter.js",
    "content": "'use strict'\n/**\n * crypter.js\n * Contains scripts for crypter.html\n ******************************/\nconst dialog = remote.dialog\nconst paths = remote.getGlobal('paths')\nconst { basename } = require('path')\nconst os = require('os')\nlet errLabel,\n  fileInput,\n  fileInputD,\n  cryptedContainer,\n  fileInputText,\n  ifileInputText,\n  crypted_template\n\n$(window).on('load', function () {\n  // Get DOM elements\n  errLabel = $('#errLabel')\n  fileInput = $('#fileInput')\n  fileInputD = document.getElementById('fileInput')\n  cryptedContainer = $('#crypted-container')\n  fileInputText = fileInput.find('#fileInputText')\n  ifileInputText = fileInputText.text()\n  // compile the crypted template\n  crypted_template = Handlebars.compile($('#crypted-template').html())\n  // attach  event\n  fileInputD.ondragover = function () {\n    return false\n  }\n  fileInputD.ondragleave = fileInputD.ondragend = function () {\n    return false\n  }\n\n  enableFileInput()\n})\n\n/* Event listeners */\nipcRenderer.on('cryptedFile', function (event, file) {\n  logger.verbose(`IPCRENDER cryptedFile emitted`)\n  let fileHTML = crypted_template(file)\n  cryptedContainer.html(fileHTML)\n  enableUI()\n  navigate('crypted')\n})\n\nipcRenderer.on('decryptedFile', function (event, file) {\n  logger.verbose(`IPCRENDER decryptedFile emitted`)\n  let fileHTML = crypted_template(file)\n  cryptedContainer.html(fileHTML)\n  enableUI()\n  navigate('crypted')\n})\n\nipcRenderer.on('cryptErr', function (event, err) {\n  logger.verbose(`IPCRENDER cryptErr emitted`)\n  errLabel.text(`ERROR: ${err}`).show()\n  enableUI()\n})\n\nipcRenderer.on('encryptingFile', function (event, file) {\n  logger.verbose(`IPCRENDER encryptingFile emitted`)\n  fileInputText.text(`Encrypting ${basename(file)}...`)\n  disableUI()\n})\n\nipcRenderer.on('decryptingFile', function (event, file) {\n  logger.verbose(`IPCRENDER decryptingFile emitted`)\n  fileInputText.text(`Decrypting ${basename(file)}...`)\n  disableUI()\n})\n\n/* Helper functions */\nfunction disableFileInput () {\n  fileInput.off('click', handler)\n  fileInput.ondrop = function () {\n    return false\n  }\n}\n\nfunction enableFileInput () {\n  fileInput.on('click', handler)\n  fileInputD.ondrop = function (e) {\n    e.preventDefault()\n    logger.info(`ONDROP fired!`)\n    if (e.dataTransfer.files[0].path) {\n      logger.info(`Got file: ${e.dataTransfer.files[0].path}`)\n      ipcRenderer.send('cryptFile', e.dataTransfer.files[0].path)\n    }\n    return false\n  }\n}\n\nfunction enableUI () {\n  fileInputText.text(ifileInputText)\n  enableFileInput()\n}\n\nfunction disableUI () {\n  disableFileInput()\n  errLabel.hide()\n}\n\nfunction showFile (path) {\n  shell.showItemInFolder(path)\n}\n\nfunction showOpenDialog (properties) {\n  // Create file input dialog\n  dialog\n    .showOpenDialog({\n      title: 'Choose a file to Encrypt',\n      defaultPath: paths.documents, // open dialog at home directory\n      properties: properties\n    })\n    .then(fileData => {\n      const filePath = fileData.filePaths\n      console.log(filePath)\n      // callback for selected file returns undefined if file not selected by user\n      if (filePath && filePath.length) {\n        // Prevent multiple input dialog\n        fileInput.off('click', handler)\n        // Select the first one\n        ipcRenderer.send('cryptFile', filePath[0])\n      } else {\n        fileInput.on('click', handler)\n      }\n    })\n}\n\nfunction handler () {\n  if (os.platform() === 'darwin') {\n    // macOS allows selecting files and folders so just show dialog\n    showOpenDialog(['openFile', 'openDirectory'])\n  } else {\n    // Windows/Linux only allow selecting either only files or folders so ask the user to choose\n    dialog.showMessageBox(\n      {\n        type: 'question',\n        message: 'What would you like to crypt?',\n        buttons: ['File', 'Folder', 'Cancel'],\n        defaultId: 1\n      },\n      function (response) {\n        switch (response) {\n          case 0:\n            // File\n            showOpenDialog(['openFile'])\n            break\n          case 1:\n            // Folder\n            showOpenDialog(['openDirectory'])\n            break\n\n          default:\n        }\n      }\n    )\n  }\n\n  return false\n}\n"
  },
  {
    "path": "app/static/js/masterpassprompt.js",
    "content": "'use strict'\n/**\n * masterpassprompt.js\n * Contains scripts for masterpassprompt.html\n ******************************/\n\nconst DONE_TIMEOUT = 2000\nlet errLabelCheckMP, errLabelSetMP, resetMasterPassInput\nconst reset = getUrlParameter('reset')\n\n$(window).on('load', function () {\n  // Get error label element\n  errLabelCheckMP = $('#checkMasterPassLabel')\n  errLabelSetMP = $('#setMasterPassLabel')\n  resetMasterPassInput = $('#resetMasterPassInput')\n\n  if (reset) {\n    navigate('reset')\n    $('footer').remove()\n  } else {\n    navigate('default')\n  }\n  // hide error label and make red\n  errLabelCheckMP.hide().css('color', COLORS.bad)\n\n  $('#checkMasterPass').click(function () {\n    validateMasterPass('checkMasterPass', errLabelCheckMP)\n  })\n\n  $('#setMasterPass').click(function () {\n    validateMasterPass('setMasterPass', errLabelSetMP)\n    // Reset validation when reset\n    errLabelCheckMP.hide()\n  })\n})\n\n/* Event listeners */\nipcRenderer.on('setMasterPassResult', function (event, err) {\n  if (err) {\n    // If error occured\n    // Display the error\n    errLabelSetMP\n      .text(`ERROR: ${err.message}`.toUpperCase())\n      .css('color', COLORS.bad)\n      .show()\n  } else {\n    // Display the MasterPass set success\n    errLabelSetMP\n      .text(RESPONSES.setSuccess)\n      .css('color', COLORS.good)\n      .show()\n    // Change note text to give further instruction and higlight it\n    $('p.note')\n      .html(RESPONSES.resetSuccess)\n      .css('color', COLORS.highlight)\n\n    // Close navigate back to chechMP after 5 seconds\n    setTimeout(function () {\n      if (reset) {\n        return app.emit('app:relaunch')\n      }\n      navigate('default')\n      errLabelSetMP.hide()\n    }, DONE_TIMEOUT)\n  }\n})\n\nipcRenderer.on('checkMasterPassResult', function (event, result) {\n  if (result.err) {\n    errLabelCheckMP.text(`ERROR: ${err.message}`).show()\n  } else if (result.match) {\n    resetMasterPassInput.hide()\n    errLabelCheckMP\n      .text(RESPONSES.correct)\n      .css('color', COLORS.good)\n      .show()\n  } else {\n    errLabelCheckMP.text(RESPONSES.incorrect).show()\n  }\n})\n"
  },
  {
    "path": "app/static/js/settings.js",
    "content": "'use strict'\n/**\n * settings.js\n * Contains scripts for settings.html\n ******************************/\n\nconst dialog = remote.dialog\nconst creds = remote.getGlobal('creds')\nconst paths = remote.getGlobal('paths')\nconst buf2hex = require('../core/crypto').buf2hex\nlet errLabel\n// let settings = remote.getGlobal(\"settings\")\n\n$(window).on('load', function () {\n  errLabel = $('#errLabel')\n  // Render the credentials\n  let creds_temp = Handlebars.compile($('#creds-template').html())\n  $('#creds').prepend(\n    creds_temp({\n      mpsalt: buf2hex(creds.mpsalt),\n      mpkhash: creds.mpkhash,\n      mpksalt: creds.mpksalt\n    })\n  )\n})\n\nipcRenderer.on('export', function () {\n  dialog\n    .showOpenDialog({\n      title: 'Choose a dir to export to',\n      defaultPath: paths.documents,\n      properties: ['openDirectory']\n    })\n    .then(function (fileData) {\n      const dirPath = fileData.filePaths\n      // callback for selected file\n      // returns undefined if file not selected by user\n      if (dirPath && dirPath.length === 1) {\n        errLabel.hide()\n        console.log(`Got dirpath ${dirPath[0]}`)\n        ipcRenderer.send('export', dirPath[0])\n      }\n    })\n})\n\nipcRenderer.on('exportResult', function (event, err) {\n  if (err) {\n    errLabel\n      .text(`ERROR: ${err.message}`.toUpperCase())\n      .css('color', COLORS.bad)\n      .show()\n  } else {\n    errLabel\n      .text(RESPONSES.exportSuccess)\n      .css('color', COLORS.good)\n      .show()\n  }\n})\n\nipcRenderer.on('import', function () {\n  dialog\n    .showOpenDialog({\n      title: 'Choose the crypter credentials file',\n      defaultPath: paths.documents,\n      properties: ['openFile']\n    })\n    .then(function (fileData) {\n      const filePath = fileData.filePaths\n      // callback for selected file\n      // returns undefined if file not selected by user\n      if (filePath && filePath.length === 1) {\n        errLabel.hide()\n        console.log(`Got filePath ${filePath[0]}`)\n        ipcRenderer.send('import', filePath[0])\n      }\n    })\n})\n\nipcRenderer.on('importResult', function (event, err) {\n  window.erro = err\n  if (err) {\n    console.log(JSON.stringify(err))\n    errLabel\n      .text(`ERROR: ${err}`.toUpperCase())\n      .css('color', COLORS.bad)\n      .show()\n  } else {\n    errLabel\n      .text(RESPONSES.importSuccess)\n      .css('color', COLORS.good)\n      .show()\n  }\n})\n"
  },
  {
    "path": "app/static/js/setup.js",
    "content": "'use strict'\n/**\n * setup.js\n * Contains scripts for setup.html\n ******************************/\n\n// Delay (in ms) after which it navigates to the done panel\nconst NAV2DONE_TIMEOUT = 2000\n// Delay (in ms) after which the setup window is closed\nconst DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000\n// Animation config\nconst SPEED = 3000\nconst OFFSET = SPEED * 1.1\nlet errLabel\n\n$(window).on('load', function () {\n  // Load jQuery marquee plugin\n  window.$.marquee = window.jQuery.marquee = require('jquery.marquee')\n  // Get error label\n  errLabel = $('#setMasterPassLabel')\n  // When DOM has loaded... hide error label and make red\n  errLabel.hide().css('color', COLORS.bad)\n\n  // attach click event listener to setMasterPass button\n  $('#setMasterPass').click(function () {\n    // Event handler function\n    validateMasterPass('setMasterPass', errLabel)\n  })\n\n  $('#done').click(function () {\n    // Close setup window and restart app\n    ipcRenderer.send('done')\n  })\n\n  // navigate to welcome screen by default\n  navigate('welcome')\n\n  /* Encryption animation */\n  $('.marquee-1').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED}).addClass('visible')\n  $('.marquee-2').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED, delayBeforeStart: OFFSET})\n\n  setTimeout(function () {\n    $('.marquee-2').addClass('visible')\n  }, OFFSET)\n})\n\n/* Event listeners */\nipcRenderer.on('setMasterPassResult', function (event, err) {\n  if (err) {\n    // If error occured Display the error\n    errLabel.text(`ERROR: ${err.message}`.toUpperCase())\n    errLabel.show()\n  } else {\n    // Display the MasterPass set success\n    errLabel.text(RESPONSES.setSuccess).css('color', COLORS.good).show()\n    setTimeout(function () {\n      // Navigate to the done panel after 2 seconds\n      navigate('done')\n    }, NAV2DONE_TIMEOUT)\n\n  // Invoke (setup) done event in main after 5 seconds So that user has time to comprehend the above change setTimeout(function() {   // Close setup window after 5 seconds   ipcRenderer.send('done') }, DONE_TIMEOUT)\n  }\n})\n"
  },
  {
    "path": "app/static/masterpassprompt.html",
    "content": "<!doctype html>\n<html>\n\n  <head>\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"styles/masterpassprompt.css\" charset=\"utf-8\">\n    <script src=\"js/common.js\" type=\"text/javascript\"></script>\n    <script src=\"js/masterpassprompt.js\" type=\"text/javascript\"></script>\n  </head>\n\n  <body>\n    <section id=\"masterpassprompt\">\n      <img class=\"header\" src=\"images/icons/masterpass.svg\"/>\n      <div class=\"panel-container\">\n        <div id=\"panel-default\" class=\"current\">\n          <header>\n            <h1>Verify the MasterPass</h1>\n            <p>Please enter your Master Password\n            </p>\n            <img class=\"info\" src=\"images/icons/info.svg\" alt=\"\"/>\n            <p class=\"info\">A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.</p>\n          </header>\n          <form onsubmit=\"return false;\">\n            <div class=\"masterpass\">\n              <input type=\"password\" name=\"masterpass\" id=\"checkMasterPassInput\" placeholder=\"********\"/>\n              <label for=\"checkMasterPassInput\" id=\"checkMasterPassLabel\"></label>\n              <a href=\"#\" id=\"resetMasterPassInput\" class=\"navigationLink\" data-panel=\"reset\">Forgot it?</a>\n            </div>\n            <button id=\"checkMasterPass\">Verify</button>\n          </form>\n        </div>\n        <div id=\"panel-reset\">\n          <header>\n            <h1>Reset the MasterPass</h1>\n            <p>Please enter a new secure MasterPass\n            </p>\n            <img class=\"info\" src=\"images/icons/info.svg\"/>\n            <p class=\"info\">The MasterPass is used to derive the data encryption keys. So any encrypted data using former MasterPass would not be decryptable.</p>\n          </header>\n          <form onsubmit=\"return false;\">\n            <div class=\"masterpass\">\n              <input type=\"password\" name=\"name\" id=\"setMasterPassInput\" placeholder=\"********\" required/>\n              <label for=\"setMasterPassInput\" id=\"setMasterPassLabel\"></label>\n            </div>\n            <button id=\"setMasterPass\">Reset</button>\n          </form>\n          <footer>\n            <a class=\"back navigationLink\" data-panel=\"default\">\n              <img src=\"images/icons/back.svg\"/>\n            </a>\n          </footer>\n        </div>\n      </div>\n    </section>\n  </body>\n\n</html>"
  },
  {
    "path": "app/static/settings.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"styles/settings.css\" />\n    <script src=\"js/common.js\" type=\"text/javascript\"></script>\n    <script src=\"js/settings.js\" type=\"text/javascript\"></script>\n  </head>\n\n  <body>\n    <section id=\"settings\">\n      <div class=\"panel-container\">\n        <div class=\"menu\">\n          <a class=\"item right navigationLink active\" data-tab=\"general\">General</a>\n          <a class=\"item navigationLink\" data-tab=\"contribute\">\n            <img class=\"icon\" src=\"images/icons/mark-github.svg\" alt=\"\" />\n          </a>\n        </div>\n        <div id=\"panel-general\" class=\"current\">\n          <section class=\"general\">\n            <header>\n              <img class=\"header\" src=\"images/icons/crypt.svg\" alt=\"\" />\n              <h3>General</h3>\n            </header>\n            <section class=\"category\">\n              <h4>Credentials</h4>\n              <div class=\"options\" id=\"creds\">\n                <div class=\"option\">\n                  <button class=\"navigationLink\" data-action=\"export\">Export</button>\n                  <button class=\"navigationLink\" data-action=\"import\">Import</button>\n                </div>\n                <div class=\"option\">\n                  <button class=\"navigationLink\" data-action=\"app:reset-masterpass\">Reset MasterPass</button>\n                </div>\n              </div>\n              <p id=\"errLabel\" class=\"info\"></p>\n            </section>\n          </section>\n        </div>\n        <div id=\"panel-contribute\">\n          <section class=\"contribute\">\n            <header>\n              <img class=\"header\" src=\"images/icons/mark-github.svg\" alt=\"\" />\n              <h3>Contribute</h3>\n            </header>\n            <div class=\"list\">\n              <div class=\"item\">\n                <a class=\"navigationLink\" data-target=\"https://github.com/HR/Crypter/issues/new\">\n                  <img src=\"images/icons/issue-opened.svg\" class=\"icon\" alt=\"\" />\n                  <div class=\"name\">\n                    Report an issue / Suggest improvements\n                  </div>\n                </a>\n              </div>\n              <div class=\"item\">\n                <a class=\"navigationLink\" data-target=\"https://github.com/HR/Crypter\">\n                  <img src=\"images/icons/star.svg\" alt=\"\" class=\"icon\" />\n                  <div class=\"name\">\n                    Star the repo\n                  </div>\n                </a>\n              </div>\n              <div class=\"item\">\n                <a class=\"navigationLink\" data-target=\"https://github.com/HR/Crypter/fork\">\n                  <img src=\"images/icons/repo-forked.svg\" alt=\"\" class=\"icon\" />\n                  <div class=\"name\">\n                    Fork the repo\n                  </div>\n                </a>\n              </div>\n              <div class=\"item\">\n                <a class=\"navigationLink\" data-action=\"app:check-update\">\n                  <img src=\"images/icons/package.svg\" alt=\"\" class=\"icon\" />\n                  <div class=\"name\">\n                    Check for Updates\n                  </div>\n                </a>\n              </div>\n            </div>\n          </section>\n        </div>\n        <footer>\n          <img src=\"images/icons/code.svg\" alt=\"\" class=\"icon\" />\n          with\n          <img src=\"images/icons/heart.svg\" alt=\"\" class=\"icon\" />\n          by\n          <a class=\"navigationLink\" style=\"margin-left: 3px\" data-target=\"https://github.com/HR\">Habib Rehman</a>\n        </footer>\n      </div>\n    </section>\n    <script id=\"creds-template\" type=\"text/x-handlebars-template\">\n      <div class='option'>\n        <span>\n          MasterPass salt\n        </span>\n        <input class='right' type='text' value=\"{{mpsalt}}\" />\n      </div>\n      <div class='option'>\n        <span>\n          MasterPassKey hash\n        </span>\n        <input class='right' type='text' value=\"{{mpkhash}}\" />\n      </div>\n      <div class='option'>\n        <span>\n          MasterPassKey salt\n        </span>\n        <input class='right' type='text' value=\"{{mpksalt}}\" />\n      </div>\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "app/static/setup.html",
    "content": "<!doctype html>\n<html>\n\n  <head>\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"styles/setup.css\" charset=\"utf-8\">\n    <script src=\"js/common.js\" type=\"text/javascript\"></script>\n    <script src=\"js/setup.js\" type=\"text/javascript\"></script>\n  </head>\n\n  <body>\n    <section id=\"setup\">\n      <div class=\"panel-container\">\n        <div id=\"panel-welcome\">\n          <header>\n            <div class=\"banner banner-left\">\n              <div class=\"marquee marquee-1\">\n                Information is the resolution of uncertainty.&nbsp;\n              </div>\n              <div class=\"marquee marquee-1\">\n                Rather be without a state than without a voice.&nbsp;\n              </div>\n              <div class=\"marquee marquee-1\">\n                Freedom is never voluntarily given by oppressors.&nbsp;\n              </div>\n              <div class=\"marquee marquee-1\">\n                Everyone has secrets to conceal; Privacy to protect.&nbsp;\n              </div>\n              <div class=\"marquee marquee-1\">\n                Crypto is the ultimate form of non-violent direct action.&nbsp;\n              </div>\n            </div>\n            <div class=\"banner banner-right\">\n              <div class=\"marquee marquee-2\">\n                aNdtYE1jkAF8i8e6LgqAHNvS5J3Jc9H6XcUlM6YUIDi8rj3a0DLl\n              </div>\n              <div class=\"marquee marquee-2\">\n                zLAgN6Umn61buZC8I6D6+mjZQW2Ap0eXJYDmAi/uGKUQ5ezHI0GI\n              </div>\n              <div class=\"marquee marquee-2\">\n                3j6Nh1XO7WTRFa+1F3i38+TKvdhLt8kS8NAJkS5U670gElWDSffT\n              </div>\n              <div class=\"marquee marquee-2\">\n                yUuJBEfzkIv6t4dcGXSoX4nOh4KFjhrdIymLpnpOlw+7VGLxqWHl\n              </div>\n              <div class=\"marquee marquee-2\">\n                fG2w7C7LKU9eoB4hKjvWJ7EhXPBbzY/+/14VqjCG/O4Kqqr2sWGb\n              </div>\n            </div>\n            <img src=\"images/icons/Crypter.svg\" alt=\"Crypter Logo\" class=\"himg\"/>\n            <h1>Welcome to Crypter</h1>\n            <p>Encrypt unlimited bits. Remember only a bit.</p>\n            <p class=\"intrfo\">An innovative, convenient and secure encryption app that simplifies password generation and management by requiring you to only remember one bit, the MasterPass.</p>\n          </header>\n          <button class=\"fancy navigationLink\" data-panel=\"masterpass\" id=\"getstarted\">GET STARTED</button>\n        </div>\n\n        <div id=\"panel-masterpass\">\n          <section id=\"masterpassprompt\">\n            <header>\n              <div class=\"banner\">\n                <img class=\"himg\" src=\"images/icons/masterpass.svg\"/>\n              </div>\n              <h1>Set a MasterPass for encryption</h1>\n              <img class=\"info\" src=\"images/icons/info.svg\" alt=\"\"/>\n              <p class=\"info\">A MasterPassKey will be derived from the MasterPass. This will then be used to derive the keys used to encrypt your data.</p>\n            </header>\n            <form onsubmit=\"return false;\">\n              <div class=\"masterpass\">\n                <input type=\"password\" id=\"setMasterPassInput\" placeholder=\"********\"/>\n                <label for=\"password\" id=\"setMasterPassLabel\"></label>\n              </div>\n              <button class=\"fancy\" id=\"setMasterPass\">SET</button>\n            </form>\n            <p class=\"note\">\n              NOTE: If lost, the MasterPass will be unverifiable! So, please store it safely.\n            </p>\n          </section>\n        </div>\n\n        <div id=\"panel-done\">\n          <header>\n            <div class=\"banner\">\n              <img src=\"images/icons/done.svg\" class=\"himg\"/>\n            </div>\n            <h1>All done!</h1>\n            <p>You have successfully setup Crypter.\n              <br/>\n              You just need to verify your MasterPass to start Crypting shortly.</p>\n          </header>\n          <button class=\"fancy done navigationLink\" id=\"done\">FINISH</button>\n          <footer>\n            <img src=\"images/icons/code.svg\" alt=\"\" class=\"icon\"/>\n            with\n            <img src=\"images/icons/heart.svg\" alt=\"\" class=\"icon\"/>\n            by\n            <a class=\"navigationLink\" data-target=\"https://github.com/HR\">Habib Rehman</a>\n          </footer>\n        </div>\n      </div>\n    </section>\n  </body>\n\n</html>"
  },
  {
    "path": "app/static/styles/crypter.css",
    "content": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,\"Lucida Grande\",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,header>p{color:#222}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}#crypt,footer{background-color:#FFF}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back>img{padding:.2rem}footer{font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}header{margin:0}h3{margin-top:0}#crypt{height:100%;min-height:20rem;width:100%;text-align:center;overflow:hidden}#crypted-container{height:84vh;overflow-y:scroll;overflow-x:hidden}.panel-container{position:relative}.panel-container>div{position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div>button{margin-top:1rem}.panel-container>div.current{width:100%;height:100vh;overflow:hidden;transform:none;position:relative}.panel-container>div.current~div{transform:translateX(110%)}.panel-container header{margin-top:2.5rem}#panel-crypt{display:flex;flex-direction:column;justify-content:space-between}#panel-crypt h1{margin:.4rem 0;font-size:1.4rem}#panel-crypt p.subtitle{margin-top:.6rem}#panel-crypt p.leadinfo{color:#9D9D9D;font-size:.8rem;margin:.4rem 1rem}#panel-crypt #fileInput{margin-top:6vh;width:100%;height:40vh;text-align:center;display:flex;flex-flow:column;cursor:pointer;align-items:center;justify-items:center}#panel-crypt #fileInput p{line-height:40vh;color:#FFF;width:70%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#panel-crypted #finfo{margin:1rem}#panel-crypted #finfo h3>div{text-align:center;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#panel-crypted #finfo table{width:100%;font-size:.8rem}#panel-crypted #finfo table tr{margin-top:.4rem}#panel-crypted #finfo table tr .bttline{vertical-align:inherit}#panel-crypted #finfo table tr .file-path{position:relative}#panel-crypted #finfo table tr .file-path img{height:16px;position:absolute;right:0;background-image:linear-gradient(to right,rgba(255,255,255,.5),#fff);padding-left:5px}#panel-crypted #finfo table tr td{vertical-align:top}#panel-crypted #finfo table tr td input[type=text]{display:table-cell;width:50vw;float:right;white-space:nowrap;text-overflow:ellipsis}#panel-crypted #finfo table tr td a>img{height:1rem;margin-left:.1rem}#panel-crypted #finfo table tr td:first-child{text-align:left;color:#7e7e7e}#panel-crypted #finfo table tr td:last-child{text-align:left;padding-left:.2rem}a[data-action='app:open-settings'] img.info{height:1.2rem;position:absolute;right:0;top:0;padding:.5rem}a.back{float:left}a.back>img{height:1rem;width:auto}footer{display:flex;clear:both;position:absolute;border-top:1px solid #DDD;width:100%;padding-bottom:0;bottom:0;left:0}footer>a{float:right;padding:.2rem}p#errLabel{display:none;display:inline-block;font-size:.8rem;padding:.2rem;color:#9f3a38}"
  },
  {
    "path": "app/static/styles/crypter.less",
    "content": "// out: ./crypter.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t ==========================================================================\n*/\n@import (less) 'mixins.less';\n/* Variable declarations*/\n@invalid-color: #9f3a38;\n@fileInput-height: 40vh;\n/* Section styles */\nheader {\n  margin: 0;\n}\n\nheader > p {\n  color: @blacker;\n}\n\nh3 {\n  margin-top: 0;\n}\n\n#crypt {\n  height: 100%;\n  min-height: 20rem;\n  width: 100%;\n  background-color: @white;\n  text-align: center;\n  overflow: hidden;\n}\n\n#crypted-container {\n  height: 84vh;\n  overflow-y: scroll;\n  overflow-x: hidden;\n}\n\n.panel-container {\n  position: relative;\n\n  & > div {\n    position: absolute;\n    text-align: center;\n    transform: translateX(-110%);\n    transition: transform 0.5s;\n\n    & > button {\n      margin-top: 1rem;\n    }\n  }\n\n  & > div.current {\n    width: 100%;\n    height: 100vh;\n    overflow: hidden;\n    transform: none;\n    position: relative;\n  }\n\n  & > div.current ~ div {\n    transform: translateX(110%);\n  }\n\n  header {\n    margin-top: 2.5rem;\n  }\n}\n\n#panel-crypt {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n\n  h1 {\n    margin: 0.4rem 0;\n    font-size: 1.4rem;\n  }\n\n  p.subtitle {\n    margin-top: 0.6rem;\n  }\n\n  p.leadinfo {\n    color: @dark;\n    font-size: 0.8rem;\n    margin: 0.4rem 1rem;\n  }\n\n  #fileInput {\n    margin-top: 6vh;\n    width: 100%;\n    height: @fileInput-height;\n    text-align: center;\n    display: flex;\n    flex-flow: column;\n    cursor: pointer;\n    align-items: center;\n    justify-items: center;\n\n    p {\n      line-height: @fileInput-height;\n      color: @white;\n      width: 70%;\n      white-space: nowrap;\n      overflow: hidden;\n      text-overflow: ellipsis;\n    }\n  }\n}\n\n#panel-crypted {\n  #finfo {\n    margin: 1rem;\n\n    h3 > div {\n      text-align: center;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n\n    table {\n      width: 100%;\n      font-size: 0.8rem;\n\n      tr {\n        margin-top: 0.4rem;\n\n        .bttline {\n          vertical-align: inherit;\n        }\n\n        .file-path {\n          position: relative;\n\n          img {\n            height: 16px;\n            position: absolute;\n            right: 0;\n            background-image: linear-gradient(to right, rgba(255,255,255,0.5), rgba(255,255,255,1));\n            padding-left: 5px;\n          }\n        }\n\n        td {\n          vertical-align: top;\n\n          input[type='text'] {\n            display: table-cell;\n            width: 50vw;\n            float: right;\n            white-space: nowrap;\n            text-overflow: ellipsis;\n          }\n\n          a > img {\n            height: 1rem;\n            margin-left: 0.1rem;\n          }\n        }\n\n        td:first-child {\n          text-align: left;\n          color: @dark * 0.8;\n        }\n\n        td:last-child {\n          text-align: left;\n          padding-left: 0.2rem;\n        }\n      }\n    }\n  }\n}\n\na[data-action='app:open-settings'] {\n  img.info {\n    height: 1.2rem;\n    position: absolute;\n    right: 0;\n    top: 0;\n    padding: 0.5rem;\n  }\n}\n\na.back {\n  float: left;\n}\n\na.back > img {\n  height: 1rem;\n  width: auto;\n}\n\nfooter {\n  display: flex;\n  clear: both;\n  position: absolute;\n  border-top: 1px solid @light;\n  width: 100%;\n  padding-bottom: 0;\n  background-color: @white;\n  bottom: 0;\n  left: 0;\n}\n\nfooter > a {\n  float: right;\n  padding: 0.2rem;\n}\n/* INVALID STYLING */\np#errLabel {\n  display: none;\n  display: inline-block;\n  font-size: 0.8rem;\n  padding: 0.2rem;\n  color: @invalid-color;\n}\n"
  },
  {
    "path": "app/static/styles/masterpassprompt.css",
    "content": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}#masterpassprompt,body,p.info{overflow:hidden}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,\"Lucida Grande\",sans-serif;font-weight:300;padding:0;height:100vh}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=password]:focus,input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,header>p{color:#222}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{display:flex;position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}header{margin:0}h1{font-size:1.4rem;margin-bottom:.1rem}button#setMasterPass{margin-top:.6rem;margin-bottom:.4rem;display:block!important}button#checkMasterPass{margin-top:2rem;display:block!important}#masterpassprompt{margin:2rem 0 0;height:100%;width:100%;background-color:#FFF;text-align:center}.panel-container{position:relative}.panel-container>div{display:block;position:absolute;text-align:center;padding:0 3rem 3rem;transform:translateX(-100%);transition:transform .3s}.panel-container>div.current~div{transform:translateX(100%)}.panel-container>div.current{transform:none;position:relative}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .3s,padding .3s;background-color:#efefef;font-size:.8rem}img.info{float:right;height:.9rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}div.masterpass input{width:98%}div.masterpass .navigationLink{margin-top:.2rem;float:right}p.note{margin-top:.2rem;font-size:.8rem}input[type=password]{margin-top:1rem;border:none;border-bottom:1px solid #DDD;outline:0}.invalid{border-color:#9F3A38!important}div.forgotMP{padding:.2rem .2rem 0 0;text-align:right}#checkMasterPass{width:100%!important;margin-top:1rem}div.masterpass>label{display:inline-block;font-size:.8rem;margin:.2rem 0;color:#9F3A38}"
  },
  {
    "path": "app/static/styles/masterpassprompt.less",
    "content": "// out: ./masterpassprompt.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t ==========================================================================\n*/\n@import (less) \"mixins.less\";\n\n/* Variable declarations*/\n@invalid-color: #9F3A38;\n\n/* Section styles */\nheader {\n  margin: 0;\n}\nheader > p {\n  color: @blacker;\n}\nh1 {\n  font-size    : 1.4rem;\n  margin-bottom: 0.1rem;\n}\n\nbutton#setMasterPass {\n  margin-top: 0.6rem;\n  margin-bottom: 0.4rem;\n  display: block !important;\n}\nbutton#checkMasterPass {\n  margin-top: 2rem;\n  display: block !important;\n}\n#masterpassprompt {\n  margin: 2rem 0 0 0;\n  height          : 100%;\n  width           : 100%;\n  background-color: @white;\n  text-align      : center;\n  overflow        : hidden;\n}\n.panel-container {\n  position: relative;\n}\n.panel-container > div {\n  display   : block;\n  position  : absolute;\n  text-align: center;\n  padding   : 0 3rem 3rem;\n  transform : translateX(-100%);\n  transition: transform 0.3s;\n}\n.panel-container > div.current ~ div {\n  transform: translateX(100%);\n}\n.panel-container > div.current {\n  transform: none;\n  position : relative;\n}\np.info {\n  display         : block;\n  width           : 100%;\n  box-sizing      : border-box;\n  max-height      : 0;\n  overflow        : hidden;\n  padding         : 0 0.5rem;\n  transition      : max-height 0.3s, padding 0.3s;\n  background-color: #efefef;\n  font-size       : 0.8rem;\n}\nimg.info {\n  float : right;\n  height: 0.9rem;\n}\nimg.info:hover + p.info {\n  max-height    : 10rem;\n  padding-top   : 0.5rem;\n  padding-bottom: 0.5rem;\n}\ndiv.masterpass {\n  input {\n    width: 98%;\n  }\n  .navigationLink {\n    margin-top: 0.2rem;\n    float: right;\n  }\n}\n\np.note {\n  margin-top: 0.2rem;\n  font-size: 0.8rem;\n}\n\ninput[type=password] {\n  margin-top   : 1rem;\n  border       : none;\n  border-bottom: 1px solid @light;\n  outline      : none;\n  &:focus {\n    animation-name    : colorTrans;\n    animation-duration: 2s;\n    border-bottom     : 1px solid @black;\n  }\n}\n.invalid {\n  border-color: @invalid-color !important;\n}\ndiv.forgotMP {\n  padding   : 0.2rem 0.2rem 0 0;\n  text-align: right;\n}\n#checkMasterPass {\n  width: 100% !important;\n  margin-top      : 1rem;\n}\n\n/* INVALID STYLING */\ndiv.masterpass > label {\n  display  : inline-block;\n  font-size: 0.8rem;\n  margin   : 0.2rem 0;\n  color    : @invalid-color;\n}\n"
  },
  {
    "path": "app/static/styles/mixins.css",
    "content": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,\"Lucida Grande\",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;color:#222;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{display:flex;position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;font-size:.8rem;align-items:center;justify-content:left;background:#FFF}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}"
  },
  {
    "path": "app/static/styles/mixins.less",
    "content": "// out: ./mixins.css, compress: true\n@import (css) \"../../node_modules/normalize.css/normalize.css\";\n\n/* theme colors */\n@white: #FFFFFF;\n@light: #DDDDDD;\n@dark: #9D9D9D;\n@darker: #555555;\n@black: #333333;\n@blacker: #222222;\n@invalid-color: #F05A5C;\n@orange: #EEA849;\n@orange_d: #F46B45;\n@ic-size: 1.4rem;\n@max-height: 100vh;\n\n/* Animations */\n@keyframes colorTrans {\n  from {\n    border-bottom: 1px solid @light;\n  }\n  to {\n    border-bottom: 1px solid @black;\n  }\n}\n\n/* General (shared) styles */\nbody {\n  font-family     : \"Roboto\", \"HelveticaNeue-Light\", \"Helvetica Neue Light\", \"Helvetica Neue\", Helvetica, \"Lucida Grande\", sans-serif;\n  font-weight     : 300;\n  margin          : 0;\n  padding         : 0;\n  background-color: @white;\n  width           : 100%;\n  height          : 100vh;\n  overflow        : hidden;\n}\np {\n  margin: 0;\n}\ndiv.none {\n  display: none;\n}\n.left {\n  -webkit-align-self: flex-start;\n  align-self        : flex-start !important;\n}\n.right {\n  margin-left: auto !important;\n}\nbutton, .button {\n  background-color: @white;\n  border          : 1px solid @light;\n  width           : 100%;\n  padding         : 0.3rem;\n  font-weight     : 400;\n  outline         : none;\n  &:active,\n  &:hover {\n    background-color: @light;\n  }\n}\nbutton.fancy {\n  margin-top     : 0.2rem;\n  border         : none;\n  color          : @white;\n  width          : 30% !important;\n  background     : linear-gradient(to right, @orange, @orange_d);\n  background-size: 200% 200%;\n  animation      : OrangeAnimGrad 3s ease infinite;\n  &:active,\n  &:hover {\n    opacity: 0.8;\n  }\n}\n.fancy {\n  border         : none;\n  color          : @white;\n  background     : linear-gradient(to right, @orange, @orange_d);\n  background-size: 200% 200%;\n  animation      : OrangeAnimGrad 3s ease infinite;\n  &:active,\n  &:hover {\n    opacity: 0.8;\n  }\n}\ninput[type=text] {\n  border: none;\n  border-bottom: 1px solid @light;\n  outline: none;\n  vertical-align: top;\n\n  &:focus {\n    animation-name: colorTrans;\n    animation-duration: 2s;\n    border-bottom: 1px solid @black;\n  }\n}\nimg.header {\n  width: 7rem;\n  height: auto;\n  bottom: 0;\n}\nimg.icon {\n  width : @ic-size;\n  height: @ic-size;\n}\nimg.info {\n  padding-left: 0.2rem;\n  height      : 0.8rem;\n}\np.intrfo {\n  color: @dark;\n  font-size: 0.8rem;\n  margin: 0.4rem;\n}\np.info {\n  display         : block;\n  width           : 100%;\n  box-sizing      : border-box;\n  max-height      : 0;\n  padding         : 0 0.5rem;\n  transition      : max-height 0.5s, padding 0.3s;\n  background-color: @light;\n  font-size       : 0.8rem;\n}\nimg.info:hover + p.info {\n  max-height    : 10rem;\n  padding-top   : 0.5rem;\n  padding-bottom: 0.5rem;\n}\na {\n  cursor: pointer;\n  cursor: hand;\n  font-size      : 0.8rem;\n  color          : @blacker;\n  text-decoration: none;\n  outline        : none;\n}\na.back {\n  float: left;\n}\na.back > img {\n  height: 1rem;\n  padding: 0.2rem;\n  width : auto;\n}\nfooter {\n  display: flex;\n  position: absolute;\n  border-top: 1px solid @light;\n  width: 100%;\n  bottom: 0;\n  left: 0;\n  font-size: 0.8rem;\n  align-items: center;\n  justify-content: left;\n  background: @white;\n}\n/* font declarations */\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Italic.eot');\n  src        : url('../fonts/Roboto-Italic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Italic.woff') format('woff'), url('../fonts/Roboto-Italic.ttf') format('truetype');\n  font-weight: normal;\n  font-style : italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-BlackItalic.eot');\n  src        : url('../fonts/Roboto-BlackItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BlackItalic.woff') format('woff'), url('../fonts/Roboto-BlackItalic.ttf') format('truetype');\n  font-weight: 900;\n  font-style : italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Bold.eot');\n  src        : url('../fonts/Roboto-Bold.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Bold.woff') format('woff'), url('../fonts/Roboto-Bold.ttf') format('truetype');\n  font-weight: bold;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Thin.eot');\n  src        : url('../fonts/Roboto-Thin.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Thin.woff') format('woff'), url('../fonts/Roboto-Thin.ttf') format('truetype');\n  font-weight: 100;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Medium.eot');\n  src        : url('../fonts/Roboto-Medium.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Medium.woff') format('woff'), url('../fonts/Roboto-Medium.ttf') format('truetype');\n  font-weight: 500;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Light.eot');\n  src        : url('../fonts/Roboto-Light.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Light.woff') format('woff'), url('../fonts/Roboto-Light.ttf') format('truetype');\n  font-weight: 300;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Regular.eot');\n  src        : url('../fonts/Roboto-Regular.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Regular.woff') format('woff'), url('../fonts/Roboto-Regular.ttf') format('truetype');\n  font-weight: normal;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-ThinItalic.eot');\n  src        : url('../fonts/Roboto-ThinItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-ThinItalic.woff') format('woff'), url('../fonts/Roboto-ThinItalic.ttf') format('truetype');\n  font-weight: 100;\n  font-style : italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-BoldItalic.eot');\n  src        : url('../fonts/Roboto-BoldItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-BoldItalic.woff') format('woff'), url('../fonts/Roboto-BoldItalic.ttf') format('truetype');\n  font-weight: bold;\n  font-style : italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-Black.eot');\n  src        : url('../fonts/Roboto-Black.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-Black.woff') format('woff'), url('../fonts/Roboto-Black.ttf') format('truetype');\n  font-weight: 900;\n  font-style : normal;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-MediumItalic.eot');\n  src        : url('../fonts/Roboto-MediumItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-MediumItalic.woff') format('woff'), url('../fonts/Roboto-MediumItalic.ttf') format('truetype');\n  font-weight: 500;\n  font-style : italic;\n}\n@font-face {\n  font-family: 'Roboto';\n  src        : url('../fonts/Roboto-LightItalic.eot');\n  src        : url('../fonts/Roboto-LightItalic.eot?#iefix') format('embedded-opentype'), url('../fonts/Roboto-LightItalic.woff') format('woff'), url('../fonts/Roboto-LightItalic.ttf') format('truetype');\n  font-weight: 300;\n  font-style : italic;\n}\n"
  },
  {
    "path": "app/static/styles/settings.css",
    "content": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.fancy,button.fancy{animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}.button,body,button{background-color:#FFF;width:100%}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,\"Lucida Grande\",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}div.none{display:none}.left{align-self:flex-start!important;-webkit-align-self:flex-start}.right{margin-left:auto!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;color:#FFF;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;color:#FFF;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.info{padding-left:.2rem;height:.8rem}p.intrfo{color:#9D9D9D;font-size:.8rem;margin:.4rem}a,a:not(.navigationLink){color:#222;outline:0;text-decoration:none;font-size:.8rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;padding:0 .5rem;transition:max-height .5s,padding .3s;background-color:#DDD;font-size:.8rem}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}.panel-container{overflow:hidden;position:relative;height:100vh}.panel-container>div:not(.menu){position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div:not(.menu)>button{margin-top:1rem}.panel-container>div.current{width:100%;min-height:100vh;transform:none;position:relative;display:flex;flex-direction:column}.panel-container>div.current section{height:90vh}.panel-container>div.current~div{transform:translateX(110%)}section#settings{min-height:100%;position:relative;background-color:#FFF}p#errLabel{text-align:center}.menu{display:flex;margin-left:0;margin-right:0;align-self:flex-end;transition:color .1s ease}.menu:after{content:'';display:block;height:0;clear:both;visibility:hidden}.menu .item:after{display:none}.menu .item{position:relative;cursor:pointer;line-height:1.4rem;text-decoration:none;-webkit-tap-highlight-color:transparent;-webkit-box-flex:0;flex:0 0 auto;-webkit-user-select:none;user-select:none;padding:.7rem .8rem 0;margin-right:.4rem;text-transform:none;color:rgba(0,0,0,.87);transition:background .1s ease,box-shadow .1s ease,color .1s ease}div.item,footer{align-items:center;display:flex}.active.item,.menu .active.item{box-shadow:none;color:rgba(0,0,0,.95)}.menu .active.item{border-bottom:2px solid rgba(0,0,0,.8);font-weight:400}a.item:active{border-bottom:2px solid rgba(0,0,0,.1);margin-bottom:-2px}.active.item{background-color:transparent;border-color:#333;font-weight:700;margin-bottom:-2px}div.item,h4,section.contribute header{border-bottom:1px solid #DDD}.item.right{display:flex;margin-left:auto!important}img.icon{width:1.4rem;height:1.4rem}img.header{width:6rem;height:auto;bottom:0}h3{margin:.5rem 0;font-weight:400}h4,section.contribute div.list .item .name{font-weight:300}h4{margin-top:.5rem;margin-bottom:.5rem;text-align:left;padding-bottom:5px}header img.icon{width:6rem;height:6rem}footer{position:absolute;border-top:1px solid #DDD;width:100%;bottom:0;left:0;padding:.3rem 0;font-size:.8rem;justify-content:center;background:#FFF}footer>img{height:1rem!important}input[type=text]{width:15rem}section.category{text-align:left;padding:1rem}div.options{margin-left:1rem}div.option{display:flex;margin-top:.2rem;font-size:.8rem}div.list{height:10rem;width:100%;overflow-y:auto}div.item{padding:.4rem 0;text-align:left}section.general{width:90%;margin:auto}section.general button{margin:.4rem 0}section.general button:nth-child(1){margin-right:.4rem}section.contribute{width:70%;margin:auto}section.contribute div.list{width:100%;margin-bottom:3rem;height:100%}section.contribute div.list .item a{display:flex;align-items:center;text-align:left}section.contribute div.list .item .icon{padding:.4rem}section.crypto{width:90%;margin:1rem auto auto}section.crypto header>img{height:4rem!important;width:auto}section.crypto button{width:20%}"
  },
  {
    "path": "app/static/styles/settings.less",
    "content": "// out: ./settings.css, compress: true\n/* Settings styles\n\t ==========================================================================\n*/\n@import (less) \"mixins.less\";\n/* Variable declarations*/\n@list-height: 10rem;\n@border-width: 2px;\n@spacing04: 0.4rem;\n@ic-size: 1.4rem;\n@header-ic-size: 6rem;\n\n.panel-container {\n  overflow: hidden;\n  position: relative;\n  height: 100vh;\n\n  & > div:not(.menu) {\n    position: absolute;\n    text-align: center;\n    transform: translateX(-110%);\n    transition: transform 0.5s;\n\n    & > button {\n      margin-top: 1rem;\n    }\n  }\n\n  & > div.current {\n    width: 100%;\n    min-height: 100vh;\n    transform: none;\n    position: relative;\n    display: flex;\n    flex-direction: column;\n\n    section {\n      height: 90vh;\n    }\n  }\n\n  & > div.current ~ div {\n    transform: translateX(110%);\n  }\n}\n\nsection#settings {\n  min-height: 100%;\n  position: relative;\n  background-color: @white;\n}\n\np#errLabel {\n  text-align: center;\n}\n\na:not(.navigationLink) {\n  font-size: 0.8rem;\n  color: @blacker;\n  text-decoration: none;\n  outline: none;\n}\n\n.left {\n  -webkit-align-self: flex-start;\n  align-self: flex-start;\n}\n\n.right {\n  margin-left: auto;\n}\n\n.menu {\n  display: flex;\n  margin-left: 0;\n  margin-right: 0;\n  align-self: flex-end;\n  transition: color 0.1s ease;\n\n  &:after {\n    content: '';\n    display: block;\n    height: 0;\n    clear: both;\n    visibility: hidden;\n  }\n\n  .item:after {\n    display: none;\n  }\n\n  .item {\n    position: relative;\n    cursor: pointer;\n    line-height: 1.4rem;\n    text-decoration: none;\n    -webkit-tap-highlight-color: transparent;\n    -webkit-box-flex: 0;\n    flex: 0 0 auto;\n    -webkit-user-select: none;\n    user-select: none;\n    padding: 0.7rem 0.8rem 0rem;\n    margin-right: 0.4rem;\n    text-transform: none;\n    color: rgba(0, 0, 0, 0.87);\n    transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease;\n  }\n\n  .active.item {\n    border-bottom: @border-width solid rgba(0, 0, 0, 0.8);\n    color: rgba(0, 0, 0, 0.95);\n    font-weight: normal;\n    box-shadow: none;\n  }\n}\n\na.item:active {\n  border-bottom: @border-width solid rgba(0, 0, 0, 0.1);\n  margin-bottom: -@border-width;\n}\n\n.active.item {\n  background-color: transparent;\n  box-shadow: none;\n  border-color: @black;\n  font-weight: 700;\n  margin-bottom: -@border-width;\n  color: rgba(0,0,0,.95);\n}\n\n.item.right {\n  display: flex;\n  margin-left: auto !important;\n}\n\nimg.icon {\n  width: @ic-size;\n  height: @ic-size;\n}\nimg.header {\n  width: 6rem;\n  height: auto;\n  bottom: 0;\n}\n\nh3 {\n  margin: 0.5rem 0;\n  font-weight: normal;\n}\n\nh4 {\n  margin-top: 0.5rem;\n  margin-bottom: 0.5rem;\n  font-weight: 300;\n  text-align: left;\n  border-bottom: 1px solid @light;\n  padding-bottom: 5px;\n}\n\nheader {\n  img.icon {\n    width: @header-ic-size;\n    height: @header-ic-size;\n  }\n}\n\nfooter {\n  display: flex;\n  position: absolute;\n  border-top: 1px solid @light;\n  width: 100%;\n  bottom: 0;\n  left: 0;\n  padding: 0.3rem 0;\n  font-size: 0.8rem;\n  align-items: center;\n  justify-content: center;\n  background: @white;\n\n  & > img {\n    height: 1rem !important;\n  }\n}\n\ninput[type=text] {\n  width: 15rem;\n}\n\nsection.category {\n  text-align: left;\n  padding: 1rem;\n}\n\ndiv.options {\n  margin-left: 1rem;\n}\n\ndiv.option {\n  display: flex;\n  margin-top: 0.2rem;\n  font-size: 0.8rem;\n}\n\ndiv.list {\n  height: @list-height;\n  width: 100%;\n  overflow-y: auto;\n}\n\ndiv.item {\n  display: flex;\n  align-items: center;\n  border-bottom: 1px solid @light;\n  padding: @spacing04 0;\n  text-align: left;\n}\n/* Panel styles\n* =================\n*/\nsection.general {\n  width: 90%;\n  margin: auto;\n  button {\n    margin: 0.4rem 0;\n  }\n  button:nth-child(1) {\n    margin-right: 0.4rem;\n  }\n}\n\nsection.contribute {\n  width: 70%;\n  margin: auto;\n\n  header {\n    border-bottom: 1px solid @light;\n  }\n\n  div.list {\n    width: 100%;\n    margin-bottom: 3rem;\n    height: 100%;\n\n    .item {\n      a {\n        display: flex;\n        align-items: center;\n        text-align: left;\n      }\n\n      .name {\n        font-weight: 300;\n      }\n\n      .icon {\n        padding: 0.4rem;\n      }\n    }\n  }\n}\n\nsection.crypto {\n  width: 90%;\n  margin: 1rem auto auto;\n\n  header > img {\n    height: 4rem !important;\n    width: auto;\n  }\n\n  button {\n    width: 20%;\n  }\n}"
  },
  {
    "path": "app/static/styles/setup.css",
    "content": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width:100%}.fancy,button.fancy{color:#FFF;animation:OrangeAnimGrad 3s ease infinite}.fancy:active,.fancy:hover,button.fancy:active,button.fancy:hover{opacity:.8}.panel-container>div>button,div.none{display:none}@keyframes colorTrans{from{border-bottom:1px solid #DDD}to{border-bottom:1px solid #333}}body{font-family:Roboto,HelveticaNeue-Light,\"Helvetica Neue Light\",\"Helvetica Neue\",Helvetica,\"Lucida Grande\",sans-serif;font-weight:300;padding:0;height:100vh;overflow:hidden}.left{-webkit-align-self:flex-start;align-self:flex-start!important}.button,button{border:1px solid #DDD;padding:.3rem;font-weight:400;outline:0}.button:active,.button:hover,button:active,button:hover{background-color:#DDD}button.fancy{margin-top:.2rem;border:none;width:30%!important;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}.fancy{border:none;background:linear-gradient(to right,#EEA849,#F46B45);background-size:200% 200%}p,p.intrfo{color:#9D9D9D}input[type=text]{border:none;border-bottom:1px solid #DDD;outline:0;vertical-align:top}input[type=text]:focus{animation-name:colorTrans;animation-duration:2s;border-bottom:1px solid #333}img.header{width:7rem;height:auto;bottom:0}img.icon{width:1.4rem;height:1.4rem}img.info{padding-left:.2rem}p.intrfo{font-size:.8rem;margin:.4rem}a,header p{color:#222}img.info:hover+p.info{max-height:10rem;padding-top:.5rem;padding-bottom:.5rem}a{cursor:pointer;cursor:hand;font-size:.8rem;text-decoration:none;outline:0}a.back{float:left}a.back>img{height:1rem;padding:.2rem;width:auto}footer{font-size:.8rem;align-items:center;justify-content:left;background:#FFF}#panel-done footer,.banner,div.item>a{align-items:center}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Italic.eot);src:url(../fonts/Roboto-Italic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Italic.woff) format('woff'),url(../fonts/Roboto-Italic.ttf) format('truetype');font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BlackItalic.eot);src:url(../fonts/Roboto-BlackItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BlackItalic.woff) format('woff'),url(../fonts/Roboto-BlackItalic.ttf) format('truetype');font-weight:900;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Bold.eot);src:url(../fonts/Roboto-Bold.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Bold.woff) format('woff'),url(../fonts/Roboto-Bold.ttf) format('truetype');font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Thin.eot);src:url(../fonts/Roboto-Thin.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Thin.woff) format('woff'),url(../fonts/Roboto-Thin.ttf) format('truetype');font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Medium.eot);src:url(../fonts/Roboto-Medium.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Medium.woff) format('woff'),url(../fonts/Roboto-Medium.ttf) format('truetype');font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Light.eot);src:url(../fonts/Roboto-Light.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Light.woff) format('woff'),url(../fonts/Roboto-Light.ttf) format('truetype');font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Regular.eot);src:url(../fonts/Roboto-Regular.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Regular.woff) format('woff'),url(../fonts/Roboto-Regular.ttf) format('truetype');font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-ThinItalic.eot);src:url(../fonts/Roboto-ThinItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-ThinItalic.woff) format('woff'),url(../fonts/Roboto-ThinItalic.ttf) format('truetype');font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-BoldItalic.eot);src:url(../fonts/Roboto-BoldItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-BoldItalic.woff) format('woff'),url(../fonts/Roboto-BoldItalic.ttf) format('truetype');font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-Black.eot);src:url(../fonts/Roboto-Black.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-Black.woff) format('woff'),url(../fonts/Roboto-Black.ttf) format('truetype');font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(../fonts/Roboto-MediumItalic.eot);src:url(../fonts/Roboto-MediumItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-MediumItalic.woff) format('woff'),url(../fonts/Roboto-MediumItalic.ttf) format('truetype');font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(../fonts/Roboto-LightItalic.eot);src:url(../fonts/Roboto-LightItalic.eot?#iefix) format('embedded-opentype'),url(../fonts/Roboto-LightItalic.woff) format('woff'),url(../fonts/Roboto-LightItalic.ttf) format('truetype');font-weight:300;font-style:italic}@keyframes colorTrans{from{background-color:#FFF}to{background-color:#DDD}}@-webkit-keyframes OrangeAnimGrad{0%,100%{background-position:0 51%}50%{background-position:100% 50%}}@keyframes OrangeAnimGrad{0%,100%{background-position:0 51%}50%{background-position:100% 50%}}#setup{height:100%;width:100%;text-align:center;overflow:hidden}form,header{margin:0}header .minimal h1{margin-top:1rem;font-weight:300}#setMasterPass,h1,h3,input[type=submit]{font-weight:400}h1{font-size:1.4rem;margin-bottom:.4rem}h1+p{margin:0 2rem}.panel-container{position:relative}.panel-container>div{position:absolute;text-align:center;transform:translateX(-110%);transition:transform .5s}.panel-container>div.current{width:100%;height:100vh;overflow:hidden;transform:none;position:relative}.panel-container>div.current>button{margin-top:1rem;display:inline-block}.panel-container>div.current~div{transform:translateX(110%)}.panel-container header{padding-top:2rem}.right{margin-left:auto!important}h3{margin:.5rem 0}a.csp{padding:.6rem 0!important}a.csp img{height:1.6rem;widht:auto}footer{display:flex;clear:both;position:absolute;border-top:1px solid #DDD;width:100%;padding-bottom:0;background-color:#FFF;bottom:0;left:0}footer>a{float:right;padding:.2rem}.himg{width:9rem;height:auto;z-index:1000}div.list{width:100%;overflow:auto}div.item>a{display:flex;border-bottom:1px solid #DDD;padding:.4rem 0;text-align:left}img.info{float:right;height:.9rem}p.info{display:block;width:100%;box-sizing:border-box;max-height:0;overflow:hidden;padding:0 .5rem;transition:max-height .3s,padding .3s;background-color:#efefef;font-size:.8rem}input{width:98%}input[type=password]{margin-top:1rem;border:none;border-bottom:1px solid #DDD;outline:0}input[type=password]:focus{border-bottom:1px solid #333}input[type=submit]{margin-top:1rem;height:2rem;background-color:#DDD;border:1px solid #DDD;width:100%;outline:0}input[type=submit]:focus{border:1px solid #333}div.submit{text-align:right;background-color:#FFF;width:100%}.none{display:none}.banner{margin:1rem auto 0;position:relative;justify-content:center;width:100%}.banner-left{margin-left:-3rem;float:left}.banner-right{margin-right:-3rem;float:right}#panel-welcome .marquee{overflow:hidden;position:relative}#panel-welcome .marquee-1,#panel-welcome .marquee-2{visibility:hidden}#panel-welcome .marquee.visible{visibility:visible}#panel-welcome>header .banner{margin:2rem auto 0;position:relative;display:inline-block;width:50%;z-index:-1;color:#DDD}#panel-welcome>header .banner+.himg{margin-top:-7.5rem!important}#panel-welcome>header .himg{z-index:1000}#panel-done footer{padding:.3rem 0;text-align:center;display:flex;font-size:.8rem;justify-content:center}#panel-done footer img{height:1rem!important}#masterpassprompt{height:100%;min-height:20rem;width:60%;margin:auto}#setMasterPass{margin:.5rem 0;width:100%}#masterpassprompt p.note{margin:none;font-size:.8rem}.forgotMP{padding:.2rem .2rem 0 0;text-align:right}.masterpass>label{display:inline-block;font-size:.8rem;margin:.2rem 0;color:#F05A5C}.invalid{border-color:#F05A5C!important}"
  },
  {
    "path": "app/static/styles/setup.less",
    "content": "// out: ./setup.css, compress: true\n/* Setup styles\n\t ==========================================================================\n*/\n@import (less) \"mixins.less\";\n/* Variable declarations*/\n@border-width: 2px;\n@side-margin: 0.8rem;\n@section-width: 60%;\n@spacing04: 0.4rem;\n@spacing-ic-txt: @side-margin - 0.4rem;\n/* TODO: ADD MARGIN RELATIVELY/CONTEXTUALLY TO CORE ELEMENTS I.E. for a button, use panel-container > button*/\n/* Animations */\n@keyframes colorTrans {\n  from {\n    background-color: @white;\n  }\n  to {\n    background-color: @light;\n  }\n}\n@-webkit-keyframes OrangeAnimGrad {\n  0% {\n    background-position: 0 51%;\n  }\n  50% {\n    background-position: 100% 50%;\n  }\n  100% {\n    background-position: 0 51%;\n  }\n}\n@keyframes OrangeAnimGrad {\n  0% {\n    background-position: 0 51%;\n  }\n  50% {\n    background-position: 100% 50%;\n  }\n  100% {\n    background-position: 0 51%;\n  }\n}\n/* General styles */\n#setup {\n  height    : 100%;\n  width     : 100%;\n  text-align: center;\n  overflow  : hidden;\n}\nform {\n  margin: 0;\n}\nheader {\n  margin: 0;\n  p {\n    color: @blacker;\n  }\n  .minimal {\n    h1 {\n      margin-top : 1rem;\n      font-weight: 300;\n    }\n  }\n}\nh1 {\n  font-size    : 1.4rem;\n  margin-bottom: 0.4rem;\n  font-weight  : 400;\n}\np {\n  margin: 0;\n  color : @dark;\n}\nh1 + p {\n  margin: 0 2rem;\n}\n.panel-container {\n  position: relative;\n  & > div {\n    position  : absolute;\n    text-align: center;\n    transform : translateX(-110%);\n    transition: transform 0.5s;\n    & > button {\n      display: none;\n    }\n  }\n  & > div.current {\n\t\twidth : 100%;\n\t\theight: 100vh;\n    overflow : hidden;\n    transform: none;\n    position : relative;\n    & > button {\n      margin-top: 1rem;\n      display: inline-block;\n    }\n  }\n  & > div.current ~ div {\n    transform: translateX(110%);\n  }\n  header {\n    padding-top: 2rem;\n  }\n}\n\n// .panel-container > div {\n// \tposition: absolute;\n// \ttext-align: center;\n// \ttransform: translateX(-110%);\n// \ttransition: transform 0.5s;\n// }\n// .panel-container > div > footer {\n// \ttransform: translateX(-110%);\n// }\n//\n// .panel-container > div.current > footer {\n// \ttransform: translateX(100%);\n// }\n/* TODO DISABLE ANIMATION ON FIRST PANE */\n// #panel-welcome ~ div {\n// \ttransform: none;\n// }\n// #panel-welcome > div {\n// \ttransform: none;\n// \ttransition: none;\n// }\n.right {\n  margin-left: auto !important;\n}\nh3 {\n  margin     : 0.5rem 0;\n  font-weight: normal;\n}\na.csp {\n  padding: 0.6rem 0 !important;\n  img {\n    height: 1.6rem;\n    widht : auto;\n  }\n}\nfooter {\n  display         : flex;\n  clear           : both;\n  position        : absolute;\n  border-top      : 1px solid @light;\n  width           : 100%;\n  padding-bottom  : 0;\n  background-color: @white;\n  bottom          : 0;\n  left            : 0;\n}\nfooter > a {\n  float  : right;\n  padding: 0.2rem;\n}\n.himg {\n  width  : 9rem;\n  height : auto;\n  z-index: 1000;\n}\ndiv.list {\n  width   : 100%;\n  overflow: auto;\n}\ndiv.item > a {\n  display      : flex;\n  align-items  : center;\n  border-bottom: 1px solid @light;\n  padding      : @spacing04 0;\n  text-align   : left;\n}\nimg.info {\n  float : right;\n  height: 0.9rem;\n}\np.info {\n  display         : block;\n  width           : 100%;\n  box-sizing      : border-box;\n  max-height      : 0;\n  overflow        : hidden;\n  padding         : 0 0.5rem;\n  transition      : max-height 0.3s, padding 0.3s;\n  background-color: #efefef;\n  font-size       : 0.8rem;\n}\ninput {\n  width: 98%;\n}\ninput[type=password] {\n  margin-top   : 1rem;\n  border       : none;\n  border-bottom: 1px solid @light;\n  outline      : none;\n  &:focus {\n    border-bottom: 1px solid @black;\n  }\n}\ninput[type=submit] {\n  margin-top      : 1rem;\n  height          : 2rem;\n  font-weight     : normal;\n  background-color: @light;\n  border          : 1px solid @light;\n  width           : 100%;\n  outline         : none;\n  &:focus {\n    border: 1px solid @black;\n  }\n}\ndiv.submit {\n  text-align      : right;\n  background-color: @white;\n  width           : 100%;\n}\n.none {\n  display: none;\n}\n\n/* Panel styles\n* =================\n*/\n.banner {\n  margin         : 1rem auto 0;\n  position       : relative;\n  align-items    : center;\n  justify-content: center;\n  width          : 100%;\n}\n.banner-left {\n  margin-left: -3rem;\n  float      : left;\n}\n.banner-right {\n  margin-right: -3rem;\n  float       : right;\n}\n/* Welcome Panel styles\n* =================\n*/\n#panel-welcome {\n  .marquee {\n    overflow: hidden;\n    position: relative;\n  }\n  .marquee-1 {\n    visibility: hidden;\n  }\n  .marquee-2 {\n    visibility: hidden;\n  }\n  .marquee.visible {\n    visibility: visible;\n  }\n  & > header {\n    .banner {\n      margin  : 2rem auto 0;\n      position: relative;\n      display : inline-block;\n      width   : 50%;\n      z-index : -1;\n      color   : @light;\n    }\n    .banner + .himg {\n      margin-top: -7.5rem !important;\n    }\n    .himg {\n      z-index: 1000;\n    }\n  }\n}\n/* Done Panel styles\n* =================\n*/\n#panel-done {\n  footer {\n    padding: 0.3rem 0;\n    text-align     : center;\n    display        : flex;\n    align-items    : center;\n    font-size      : 0.8rem;\n    justify-content: center;\n    img {\n      height: 1rem !important;\n    }\n  }\n}\n\n/* MasterPass Panel styles\n* =================\n*/\n#masterpassprompt {\n  height    : 100%;\n  min-height: 20rem;\n  width     : @section-width;\n  margin    : auto;\n}\n#setMasterPass {\n  margin     : 0.5rem 0;\n  font-weight: normal;\n  width      : 100%;\n}\n#masterpassprompt p.note {\n  margin   : none;\n  font-size: 0.8rem;\n}\n.forgotMP {\n  padding   : 0.2rem 0.2rem 0 0;\n  text-align: right;\n}\n\n/* INVALID STYLING */\n.masterpass > label {\n  display  : inline-block;\n  font-size: 0.8rem;\n  margin   : 0.2rem 0;\n  color    : @invalid-color;\n}\n.invalid {\n  border-color: @invalid-color !important;\n}\n"
  },
  {
    "path": "app/utils/logger.js",
    "content": "'use strict'\n/**\n * logger.js\n * Custom logger for debugging\n ******************************/\nconst { createLogger, format, transports } = require('winston')\nconst { isRenderer } = require('./utils')\n\n// const { app } = require('electron')\nif (process.env.TEST_RUN || isRenderer()) {\n  module.exports = createLogger({\n    silent: true,\n    exitOnError: false\n  })\n} else {\n  const moment = require('moment')\n  const fs = require('fs-extra')\n  // const IN_DEV = !app.isPackaged\n  let debugDir = `${global.paths.userData}/debug`\n  fs.ensureDirSync(debugDir)\n  const fileTransport = new transports.File({\n    filename: `${debugDir}/CS_debug_${moment().format('DD.MM@HH:MM').trim()}.log`,\n    handleExceptions: true,\n    colorize: false,\n    level: 'verbose'\n  })\n\n  const logFormat = format.combine(\n    format.colorize(),\n    format.timestamp(),\n    format.prettyPrint(),\n    format.align(),\n    format.splat(),\n    format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)\n  )\n\n  const logger = createLogger({\n    transports: [\n      new transports.Console(),\n      fileTransport\n    ],\n    exitOnError: false,\n    format: logFormat,\n    level: 'verbose'\n  })\n\n  module.exports = logger\n}"
  },
  {
    "path": "app/utils/update.js",
    "content": "const https = require('https')\nconst { app, dialog, shell } = require('electron')\nconst { REPO } = require('../config')\nconst USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub'\nconst VERSION_REGEX = /[\\.v]+/g\nconst VERSION = parseV(app.getVersion())\n\nfunction parseV(str) {\n  return parseInt(str.replace(VERSION_REGEX, ''))\n}\n\nmodule.exports = {\n  checkUpdate: function () {\n    return new Promise((resolve, reject) => {\n      https.get(REPO.RELEASES_API_URL, {\n          headers: { 'User-Agent': USER_AGENT }\n        }, (res) => {\n          let data = ''\n\n          res.on('data', (chunk) => {\n            data += chunk\n          })\n\n          res.on('end', () => {\n            try {\n              release = JSON.parse(data.toString('utf8'))\n              const LATEST_VERSION = parseV(release.tag_name)\n              if (VERSION < LATEST_VERSION) {\n                dialog.showMessageBox({\n                  type: 'info',\n                  message: `Update is available.`,\n                  detail: `A new version Crypter ${release.tag_name} is available.\\nDo you want to get it?`,\n                  buttons: ['Get update', 'Later'],\n                  defaultId: 0,\n                  cancelId: 1,\n                  icon: null\n                }, (response) => {\n                  if (response === 0) {\n                    // Update button pressed\n                    shell.openExternal(release.html_url)\n                  }\n                })\n                resolve(true)\n              } else {\n                resolve(false)\n              }\n            } catch (err) {\n              reject(err)\n            }\n          })\n        })\n        .on('error', (err) => reject(err))\n    })\n  }\n\n}"
  },
  {
    "path": "app/utils/utils.js",
    "content": "const { extname } = require('path')\nconst { CRYPTO } = require('../config')\n\nmodule.exports = {\n  isRenderer: () => {\n    // running in a web browser\n    if (typeof process === 'undefined') return true\n\n    // node-integration is disabled\n    if (!process) return true\n\n    // We're in node.js somehow\n    if (!process.type) return false\n\n    return process.type === 'renderer'\n  },\n  isCryptoFile: (file) => extname(file).toLowerCase() === CRYPTO.EXT\n}"
  },
  {
    "path": "appveyor.yml",
    "content": "version: \"{build}\"\n\nplatform:\n  - x64\n\nimage: Visual Studio 2019\n\n\ncache:\n  - node_modules\n  - app\\node_modules\n  - '%APPDATA%\\npm-cache'\n  - '%USERPROFILE%\\.electron'\n\ninit:\n  - git config --global core.autocrlf input\n\n# Only build if commit message contains '[build]'\nonly_commits:\n  message: /\\[build\\]/\n\ninstall:\n  - set /p NODE_VERSION=<.nvmrc\n  - ps: Install-Product node $env:NODE_VERSION $env:PLATFORM\n  - npm install npm -g\n  - npm install electron-builder@next -g\n  - npm install --production\n  - npm prune\n\nbuild_script:\n  - node --version\n  - npm --version\n  - npm run build:win\n\n# Post-install test scripts.\n# test_script:\n#   - script\\test.cmd\ntest: off"
  },
  {
    "path": "build/license.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished TODO so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "gulpfile.js",
    "content": "const gulp = require('gulp')\nconst shell = require('child_process').exec\nconst spawn = require('child_process').spawn\nconst babel = require('gulp-babel')\nconst env = require('gulp-env')\nconst less = require('gulp-less')\nconst path = require('path')\nconst istanbul = require('gulp-babel-istanbul')\nconst injectModules = require('gulp-inject-modules')\nconst mocha = require('gulp-mocha')\nconst LessPluginCleanCSS = require('less-plugin-clean-css')\nconst cleancss = new LessPluginCleanCSS({ advanced: true })\nconst LESS_FILES = './app/static/styles/*.less'\nconst ELECTRON = __dirname + '/node_modules/.bin/electron'\nconst DEBUG = false\nlet p\nlet args = ['.']\n// Start the electron process.\nasync function electron () {\n  // kill previous spawned process\n  if (p) {\n    p.kill()\n  }\n\n  if (DEBUG) args.unshift('--inspect=5858')\n  // `spawn` a child `gulp` process linked to the parent `stdio`\n  p = await spawn(ELECTRON, args, {\n    stdio: 'inherit',\n    env: {\n      ...process.env\n    }\n  })\n}\n\ngulp.task('run', electron)\n\ngulp.task('watch', function (done) {\n  gulp.watch(LESS_FILES, gulp.series('less'))\n  gulp.watch(['./app/*.js', './app/src/*.js', './app/core/*.js'], gulp.series('run'))\n  done()\n})\n\ngulp.task('nodev', function () {\n  return shell(\n    // start electron main and render process\n    `node_modules/.bin/electron script/resolveNodeV.js`,\n    function (err, stdout, stderr) {\n      console.log(stdout)\n      console.log(stderr)\n    }\n  )\n})\n\ngulp.task('less', function () {\n  return gulp\n    .src('./app/static/styles/*.less')\n    .pipe(\n      less({\n        paths: [path.join(__dirname, 'less', 'includes')],\n        plugins: [cleancss]\n      })\n    )\n    .pipe(gulp.dest('./app/static/styles/'))\n})\n\n/* TEST */\n\ngulp.task('test', () => {\n  shell(\n    // Run test stuff\n    'mocha --require babel-core/register test/'\n  )\n})\n\ngulp.task('coverage', function (cb) {\n  const envs = env.set({\n    TEST_RUN: true\n  })\n  gulp\n    .src('./app/core/**/*.js')\n    .pipe(envs)\n    .pipe(istanbul())\n    .pipe(istanbul.hookRequire())\n    .on('finish', function () {\n      gulp\n        .src('test/*.js')\n        .pipe(babel())\n        .pipe(injectModules())\n        .pipe(mocha())\n        .pipe(istanbul.writeReports())\n        .on('end', cb)\n    })\n})\n\n/* BUILD */\n\ngulp.task('rebuildni', () => {\n  shell(\n    // start node inspector server\n    'node_modules/.bin/node-pre-gyp --target=$(node_modules/.bin/electron -v | sed s/\\v//g) --runtime=electron --fallback-to-build --directory node_modules/v8-debug/ --dist-url=https://atom.io/download/atom-shell reinstall && node_modules/.bin/node-pre-gyp --target=$(node_modules/.bin/electron -v | sed s/\\v//g) --runtime=electron --fallback-to-build --directory node_modules/v8-profiler/ --dist-url=https://atom.io/download/atom-shell reinstall'\n  )\n})\n\ngulp.task('buildnative', () => {\n  shell(\n    // start build the native module\n    './node_modules/.bin/electron-rebuild #1'\n  )\n})\n\ngulp.task('ni', () => {\n  shell(\n    // start node inspector server\n    'ELECTRON_RUN_AS_NODE=true node_modules/.bin/electron node_modules/node-inspector/bin/inspector.js'\n  )\n})\n\ngulp.task('driver', () => {\n  shell(\n    // Run chromedriver\n    './node_modules/chromedriver/bin/chromedriver'\n  )\n})\n\ngulp.task('default', gulp.series('less', 'watch', 'run'))\n"
  },
  {
    "path": "license",
    "content": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished TODO so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"Crypter\",\n  \"productName\": \"Crypter\",\n  \"version\": \"5.0.0\",\n  \"description\": \"An innovative, convenient and secure cross-platform crypto app\",\n  \"license\": \"MIT\",\n  \"repository\": \"https://github.com/HR/Crypter\",\n  \"homepage\": \"https://github.com/HR/Crypter\",\n  \"bugs\": \"https://github.com/HR/Crypter/issues\",\n  \"main\": \"./app/index.js\",\n  \"author\": {\n    \"name\": \"Habib Rehman\",\n    \"email\": \"H@Rehman.email\",\n    \"url\": \"https://git.io/HR\"\n  },\n  \"build\": {\n    \"appId\": \"com.github.hr.crypter\",\n    \"asar\": false,\n    \"files\": [\n      \"**/*\",\n      \"!**/node_modules/.bin\",\n      \"!**/._*\",\n      \"!**/{.*,.git,*.yml,*.md,README,readme,test*,*.less,gulpfile.js}\",\n      \"!script${/*}\",\n      \"!test${/*}\"\n    ],\n    \"compression\": \"normal\",\n    \"fileAssociations\": {\n      \"ext\": \"crypto\",\n      \"name\": \"CRYPTO\",\n      \"role\": \"Editor\",\n      \"description\": \"The Crypter encryption format. More info at git.io/Crypter.info#crypto-file\"\n    },\n    \"mac\": {\n      \"category\": \"public.app-category.utilities\",\n      \"publish\": \"github\",\n      \"identity\": null\n    },\n    \"dmg\": {\n      \"background\": \"build/background.tif\",\n      \"icon\": \"vicon.icns\",\n      \"iconSize\": 116,\n      \"iconTextSize\": 13,\n      \"contents\": [\n        {\n          \"x\": 240,\n          \"y\": 135\n        },\n        {\n          \"x\": 240,\n          \"y\": 400,\n          \"type\": \"link\",\n          \"path\": \"/Applications\"\n        }\n      ],\n      \"window\": {\n        \"width\": \"480\",\n        \"height\": \"540\"\n      }\n    },\n    \"linux\": {\n      \"category\": \"Utility\",\n      \"publish\": \"github\"\n    },\n    \"win\": {\n      \"icon\": \"icon.ico\",\n      \"target\": \"nsis\",\n      \"publish\": \"github\"\n    },\n    \"nsis\": {\n      \"oneClick\": true,\n      \"perMachine\": true\n    },\n    \"snap\": {\n      \"publish\": \"github\"\n    }\n  },\n  \"scripts\": {\n    \"pack\": \"electron-builder --dir\",\n    \"build:mac\": \"electron-builder -m -c.electronVersion $(npm info electron version)\",\n    \"build:lin\": \"electron-builder -l --x64 --ia32 -c.electronVersion $(npm info electron version)\",\n    \"build:win\": \"electron-builder -w --x64 --ia32\",\n    \"start\": \"electron .\",\n    \"postinstall\": \"electron-builder install-app-deps\",\n    \"nodeGypReBuild\": \"electron-rebuild .\",\n    \"electronV\": \"electron -v | sed s/\\\\v//g\",\n    \"test\": \"TEST_RUN=true mocha --require @babel/register test/test.js\",\n    \"coverage\": \"TEST_RUN=true nyc --reporter=lcov mocha -- test/test.js\",\n    \"coveralls\": \"coveralls < coverage/lcov.info\",\n    \"codeclimate\": \"node codeclimate-test-reporter < coverage/lcov.info\",\n    \"package\": \"electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage)' --prune --asar --all --version=$(npm run electronVersion)\",\n    \"winpackage\": \"chmod +x script/win-build.sh && script/win-build.sh\",\n    \"xtest\": \"npm run xtestbuild && npm run xltest\",\n    \"xpackage\": \"electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=app/icons/Crypter.icns --app-copyright=Habib_Rehman --overwrite\",\n    \"xltest\": \"unset TEST_RUN && rm -rf ~/Library/Application\\\\ Support/CrypterTest/ && mocha --require @babel/register ./test/ui/*.js\",\n    \"xtestbuild\": \"npm_package_productName=CrypterTest && electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage|backups|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=app/icons/Crypter.icns --app-copyright=HR --overwrite && npm run productNameChange\"\n  },\n  \"keywords\": [\n    \"encryption\",\n    \"decryption\",\n    \"crypto\",\n    \"end-to-end\",\n    \"client\",\n    \"electron\"\n  ],\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.11.0\",\n    \"@babel/preset-env\": \"^7.11.0\",\n    \"@babel/register\": \"^7.10.5\",\n    \"babel-cli\": \"6.26.0\",\n    \"chai\": \"4.2.0\",\n    \"chai-as-promised\": \"7.1.1\",\n    \"codeclimate-test-reporter\": \"0.5.1\",\n    \"coveralls\": \"3.1.0\",\n    \"electron\": \"9.1.2\",\n    \"electron-builder\": \"^22.8.0\",\n    \"electron-packager\": \"15.0.0\",\n    \"electron-rebuild\": \"1.11.0\",\n    \"gulp\": \"4.0.2\",\n    \"gulp-babel\": \"8.0.0\",\n    \"gulp-babel-istanbul\": \"1.6.0\",\n    \"gulp-env\": \"0.4.0\",\n    \"gulp-inject-modules\": \"1.0.0\",\n    \"gulp-json-editor\": \"2.5.4\",\n    \"gulp-less\": \"4.0.1\",\n    \"gulp-mocha\": \"7.0.2\",\n    \"isparta\": \"4.1.1\",\n    \"less-plugin-clean-css\": \"1.5.1\",\n    \"mocha\": \"8.1.0\",\n    \"mocha-lcov-reporter\": \"1.3.0\",\n    \"nyc\": \"^15.1.0\",\n    \"spectron\": \"11.1.0\"\n  }\n}\n"
  },
  {
    "path": "readme.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://github.com/HR/Crypter\"><img src=\"./build/icons/256x256.png\" alt=\"Crypter\" width=\"180\" style= \"margin-bottom: 1rem\"></a>\n  <br>\n  Crypter\n  <br>\n  <br>\n</h1>\n\n<h4 align=\"center\">An innovative, convenient and secure crypto app.</h4>\n<p align=\"center\">\n    <a href=\"https://github.com/HR/Crypter/releases/latest\">\n        <img src=\"https://img.shields.io/badge/Download-4.0-orange.svg\"\n            alt=\"Download latest release\" style= \"margin-bottom: 0.5rem\" height=\"25px\">\n    </a>\n</p>\n\n<p align=\"center\">\n    <a href=\"http://standardjs.com/\">\n        <img src=\"https://img.shields.io/badge/code%20style-standard-brightgreen.svg\"\n             alt=\"Standard JS Code Style\">\n    </a>\n    <a href=\"https://travis-ci.org/HR/Crypter\">\n        <img src=\"https://travis-ci.org/HR/Crypter.svg?branch=master\"\n             alt=\"Travis Build\">\n    </a>\n    <a href=\"https://ci.appveyor.com/project/HR/crypter\">\n        <img src=\"https://ci.appveyor.com/api/projects/status/4pa8cnlb1qnyj1xd/branch/master?svg=true\"\n             alt=\"AppVeyor Build\">\n    </a>\n    <a href=\"https://coveralls.io/github/HR/Crypter?branch=master\">\n        <img src=\"https://coveralls.io/repos/github/HR/Crypter/badge.svg?branch=master\"\n             alt=\"Test Coverage\">\n    </a>\n    <a href=\"https://codeclimate.com/github/HR/Crypter?branch=master\">\n        <img src=\"https://codeclimate.com/github/HR/Crypter/badges/gpa.svg\"\n             alt=\"CodeClimate GPA\">\n    </a>\n    <a href=\"https://github.com/HR/Crypter/releases/latest\">\n        <img src=\"https://img.shields.io/github/downloads/hr/crypter/total\"\n             alt=\"GitHub all releases downloads\" >\n    </a>\n</p>\n<br>\n\n> Encrypt unlimited bits. Remember only a bit.\n\n**Crypter** is a cross-platform crypto app that makes encryption and decryption\nconvenient while still upholding strong security. It tackles one of the weakest\nlinks in most security systems today - weak passwords. It simplifies secure\npassword generation and management and requires you to only remember one bit -\nyour MasterPass.\n\n[Crypter v4.0](https://github.com/HR/Crypter/releases/tag/v4.0.0) is a crypto\napp that can decrypt and encrypt any arbitrary data this includes files and\nfolders. This version has been released and fully tested for macOS (OSX), Linux\n(for all distros via [AppImage](http://appimage.org/)) and Windows (32 & 64\nbit). All core modules (modules that provide the core functionality) are fully\ntested.\n\n[Crypter v5.0](https://github.com/HR/Crypter/milestone/4) will save your MasterPass\nin your OS's Keychain so you won't have to enter it every time you open the app.\nTo help speed up the development please send a PR for what's left do https://github.com/HR/Crypter/milestone/4\n\nPlease open an issue if you have any suggestions and add improvements via PRs!\n\nAlso checkout [Ciphora](https://github.com/HR/ciphora) (https://github.com/HR/ciphora)\na decentralized end-to-end encrypted messaging app.\n\nLink to this README: https://git.io/Crypter.info\n\n---\n\n# Contents\n\n<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->\n\n- [Installation](#installation)\n- [Screens](#screens)\n- [Crypto](#crypto)\n  - [Encryption process](#encryption-process)\n  - [Decryption process](#decryption-process)\n  - [Public credentials](#public-credentials)\n- [CRYPTO file](#crypto-file)\n  - [Format](#format)\n  - [Reusing the same MasterPass](#reusing-the-same-masterpass)\n  - [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)\n- [Security](#security)\n  - [Security-first practice](#security-first-practice)\n  - [MasterPass](#masterpass)\n  - [MasterPassKey](#masterpasskey)\n- [FAQs](#faqs)\n  - [How do I encrypt a file or folder?](#how-do-i-encrypt-a-file-or-folder)\n  - [How do I decrypt a CRYPTO file?](#how-do-i-decrypt-a-crypto-file)\n  - [How do I encrypt multiple files?](#how-do-i-encrypt-multiple-files)\n  - [Why am I getting the \"Corrupted Crypter file or trying to decrypt on a different machine.\" error?](#why-am-i-getting-the-corrupted-crypter-file-or-trying-to-decrypt-on-a-different-machine-error)\n  - [Why can't I decrypt a CRYPTO file on a different machine with the same MasterPass?](#why-cant-i-decrypt-a-crypto-file-on-a-different-machine-with-the-same-masterpass)\n  - [Why can't I decrypt a CRYPTO file with the same MasterPass?](#why-cant-i-decrypt-a-crypto-file-with-the-same-masterpass)\n  - [Where are my encrypted/decrypted files/folders placed?](#where-are-my-encrypteddecrypted-filesfolders-placed)\n  - [How can I access Crypter's preferences?](#how-can-i-access-crypters-preferences)\n  - [How can I reset my MasterPass?](#how-can-i-reset-my-masterpass)\n  - [What is a valid MasterPass?](#what-is-a-valid-masterpass)\n  - [What are MasterPass credentials?](#what-are-masterpass-credentials)\n  - [How can I export my MasterPass credentials?](#how-can-i-export-my-masterpass-credentials)\n  - [How can I import my MasterPass credentials?](#how-can-i-import-my-masterpass-credentials)\n- [Development](#development)\n  - [Configurations](#configurations)\n  - [Install (dependencies)](#install-dependencies)\n  - [Run](#run)\n  - [Test](#test)\n  - [Build](#build)\n- [License](#license)\n\n## <!-- /TOC -->\n\n## Installation\n\nAll prebuilt binaries for all major platforms are available under\n[releases](https://github.com/HR/Crypter/releases/latest).\n\nCrypter is also on [Homebrew Cask](https://formulae.brew.sh/cask/crypter) for\nmacOS. So to install it, simply run the following command in the Terminal:\n\n```bash\n$ brew install --cask crypter@4.0.0\n```\n\n<br/>\n\n## Screens\n\n<p align=\"center\">\n  <img src=\"/.github/Welcome_screen.png?raw=true\" alt=\"Welcome screen\" width=\"100%\">\n  <img src=\"/.github/Crypter_main_screen.png?raw=true\" alt=\"Crypter screen\" width=\"40%\">\n  <img src=\"/.github/MasterPass_screen.png?raw=true\" alt=\"MasterPass screen\" width=\"40%\">\n  <img src=\"/.github/Settings_screen.png?raw=true\" alt=\"Settings screen\" width=\"85%\">\n</p>\n\n<br/>\n\n## Crypto\n\n> One key to derive them all!\n\nCrypter derives a MasterPassKey from the MasterPass obtained at setup by using\nthe PBKDF2 key derivation algorithm (see below for the specification). It\nthen uses PBKDF2 to derive a number of encryption keys from the MasterPassKey\nthat can be used for the encryption of files. This method allows for the\ngeneration of very secure encryption keys for data encryption. Moreover, by\npublicly storing the credentials used to derive the MasterPassKey and the salts\nused to derive the encryption keys, you are able to produce the encryption keys\nat will and without needing to store them securely. Your MasterPass is the only\nthing that you need to remember.\n\nCrypter never directly encrypts anything with your MasterPass. Instead, it\nderives a MasterPassKey from it, which it then uses to derive the\nencryption key used to encrypt your file. Every time a file is decrypted,\nthe encryption key is re-derived from the MasterPassKey. Every time you set the\nMasterPass through the setup or reset it through Verify MasterPass, the\nMasterPassKey is derived from the MasterPass using a newly generated set of\n(random) credentials. These credentials are used to re-derive the MasterPassKey\nevery time that Crypter is executed (i.e. the app is launched).\n\nAuthentication with the AES-256-GCM symmetric block cipher is used by default.\nThis ensures that data integrity is verified on decryption and allows the app\nto detect tampering or data corruption.\n\nThe following are the crypto defaults and can be found under `app/config.js`:\n\n```js\n// Crypto defaults\n{\n  ITERATIONS: 50000, // file encryption key derivation iterations\n  KEYLENGTH: 32, // encryption key length\n  IVLENGTH: 12, // initialisation vector length\n  ALGORITHM: 'aes-256-gcm', // encryption algorithm\n  DIGEST: 'sha256', // digest function\n  HASH_ALG: 'sha256', // hashing function\n  MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations\n}\n```\n\n### Encryption process\n\nWhen encrypting a file, Crypter first creates a temporary hidden directory,\nnamely '.crypting'. It then encrypts the user-selected file with the crypto\ndefaults and flushes the encrypted data to a file in the directory, namely\n'data'. If it is a directory then it is compressed first (tar). It also writes\nthe public credentials to a file within the same directory, namely 'creds'.\nFinally, Crypter compresses the directory to a tar archive with the name of the\nuser-selected file and the '.crypto' extension appended to it.\n\n### Decryption process\n\nThe decryption process is essentially the inverse of the encryption process.\nDuring decryption, Crypter creates a temporary hidden directory named\n'.decrypting'. It then reads the credentials from the creds file and decrypts\nthe data file into the original file or directory (after decompressing it) with\nits original name and extension, as deduced from the CRYPTO file name (e.g. the\nextension for \"file.txt.crypto\" would be \".txt\").\n\n### Public credentials\n\nCertain credentials are required to decrypt the encrypted data. These are\nneeded to reconstruct the particular encryption key and to verify data integrity.\nThese can be stored publicly without compromising security since it is fairly\nimpossible (by current standards) to reconstruct the encryption key without the\nMasterPass and its credentials. These credentials are stored in the creds file of\nthe [CRYPTO file](#crypto-file) archive (as delineated above) in the following\nformat:\n\n#### v1\n\n```\nCrypter#iv#authTag#salt#dir\n```\n\n#### v2\n\nUses JSON\n\n```json\n{\n  \"type\": \"CRYPTO\",\n  \"iv\": \"...\",\n  \"authTag\": \"...\",\n  \"salt\": \"...\",\n  \"isDir\": true || false\n}\n```\n\nThe `dir` part is only included for directories\n<br/>\n\n## CRYPTO file\n\n### Format\n\nA CRYPTO file is the product of the Crypter encryption process. This file\nstores both the encrypted version of the user file and the public credentials\nneeded to encrypt and decrypt it. It has a `.crypto` file extension, which\nis appended to the full file name (including the extension) of the file\noriginally encrypted. The file itself is a tar archive in the following\nstructure:\n\n```c\nsomeFile.crypto\n├── data // the encrypted version of the user selected file\n└── creds // the public credentials used to encrypt it\n```\n\n### Reusing the same MasterPass\n\nIf you attempt to decrypt a CRYPTO file by _resetting to a specific\nMasterPass_ or _setting an identical MasterPass on a different machine_,\nyou will likely encounter the following error:\n\n```\nERROR: Unsupported state or unable to authenticate data\n```\n\nThis issue occurs because the MasterPassKey that was originally used to\nderive the encryption key on is **not the same** as the MasterPassKey\nderived with the reused MasterPass. Since Crypter uses randomness to\ngenerate secure credentials, this second set of credentials will be quite\n**different** from the original set. As a result, the derived encryption key is\nincorrect and yields this error.\n\nSee [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)\nfor instructions on how to successfully reuse the same MasterPass.\n\n### Achieving portability and same MasterPass reuse\n\nTo achieve portability on Crypter, the set of MasterPassKey credentials\nneed to be exported from Crypter on the source machine<sup>[1](#source)</sup>\nand imported into Crypter on the target machine<sup>[2](#target)</sup>.\n\nThis can be achieved in two simple steps:\n\n<ol>\n  <li><a href=\"#how-can-i-export-my-masterpass-credentials\">Export MasterPass credentials on the source machine</a><sup><a href=\"#source\">1</a></sup></li>\n  <li><a href=\"#how-can-i-import-my-masterpass-credentials\">Import MasterPass credentials on the target machine</a><sup><a href=\"#target\">2</a></sup></li>\n</ol>\n\nPlease refer to the FAQs for instructions on how to perform the above steps.\n\n<hr>\n\n<a name=\"source\"></a> [1] The machine where the CRYPTO file was initially\nencrypted.\n\n<a name=\"target\"></a> [2] The machine where you wish to decrypt the CRYPTO\nfile.\n\n<br/>\n\n## Security\n\n### Security-first practice\n\nCrypter follows a security-first practice. This means that security is its\nhighest priority and first consideration. Thus, while Crypter seeks\nto make encryption more convenient, it always defers to maintaining\na high level of security.\n\n### MasterPass\n\nCrypter never stores your MasterPass in memory or on the filesystem. This\nsubstantially improves the security of your MasterPass. You are only asked to\nenter the MasterPass when you first set, reset or verify it. Whenever you enter\nyour MasterPass, Crypter derives a MasterPassKey (using a set of generated\ncredentials) and then immediately discards the MasterPass. The MasterPassKey is\nthen securely stored in memory and used to derive the encryption keys. Since\nthese credentials are derived via a one-way function, they cannot be used in\nany way to derive the MasterPass.\n\n### MasterPassKey\n\nCrypter uses a WeakMap to store the MasterPassKey inside the MasterPassKey class\nusing a closure function. This makes the MasterPassKey data held in the object\n(externally) inaccessible, consequently increasing the protection of the\nMasterPassKey. The MasterPassKey is never flushed to the filesystem and is always\nstored in (main) memory. Since JS does not give control over or allow such a\nlow-level operation as wiping memory, the program relies on the garbage\ncollection and volatility of the main memory for the permanent erasure of the\nMasterPassKey stored in memory.\n\nA decent number of iterations (see the above specifications) are used in the\nderivation of the MasterPassKey to mitigate brute-force attacks. A good\namount of iterations are also used during the derivation of the encryption\nkeys from the MasterPassKey. Consequently, performance and speed are not\nsignificantly compromised. For critical applications, you may choose to\nselect 10,000,000 iterations instead of the default number\n(in app/core/crypto.js). Please refer to\nhttp://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf for\nmore information.\n\nCrypter generates a new set of random credentials for deriving the MasterPassKey\nevery time the MasterPass is set (at setup) or reset. Crypter employs randomness\nto mitigate brute-force attacks and thus drastically improve security.\n\n<br/>\n\n## FAQs\n\n### How do I encrypt a file or folder?\n\nIf you haven't already, walk through the setup and set your MasterPass.\nTo encrypt a file or folder, first launch Crypter and verify your MasterPass.\nAfter doing so successfully, you will see the main Crypter window with an orange\narea. Here, you can simply drag-and-drop or click to select the file/folder you\nwish to encrypt. Once Crypter is done encrypting your file/folder, it will show\nyou the encryption information (i.e. the encryption key and the path of the\nencrypted file) in a new window. To encrypt another file/folder simply click the\nback arrow at the bottom left and start-over ;)\n\n### How do I decrypt a CRYPTO file?\n\n> The following instructions assume that the CRYPTO file that you wish to\n> decrypt is being used with the same MasterPass that you set at setup and also\n> that you have not reset it since that time. If this is not the case, please refer\n> to [Reusing the same MasterPass](#reusing-the-same-masterpass).\n\nTo decrypt a CRYPTO file, first launch Crypter and verify your MasterPass. After\ndoing so successfully, you will see the main Crypter window with an orange area.\nHere, you can simply drag-and-drop or click to select the CRYPTO file that you\nwish to decrypt. After a few seconds, the process will complete and you will see\nsome information about the file and its original encryption in a new window. By\ndefault, the decrypted file has the same name as the name of the original file\n(i.e. the encrypted file name without the `.crypto` at the end).\n\n### How do I encrypt multiple files?\n\nCrypter can encrypt an entire folder so you can put them in a folder or,\nalternatively, compress them into an archive (like a `.zip`) and then just pass\nit to Crypter ;)\n\n### Why am I getting the \"Corrupted Crypter file or trying to decrypt on a different machine.\" error?\n\nThis error means that either your Crypter file (i.e. the `data` file) is\ncorrupt/tempered, that you are on a different machine than the one originally\nused to encrypt the file or that you have previously reset your MasterPass.\nFor the last two cases, please refer to\n[Reusing the same MasterPass](#reusing-the-same-masterpass) and\n[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse).\n\n### Why can't I decrypt a CRYPTO file on a different machine with the same MasterPass?\n\nPlease refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and\n[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)\n\n### Why can't I decrypt a CRYPTO file with the same MasterPass?\n\nPlease refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and\n[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)\n\n### Where are my encrypted/decrypted files/folders placed?\n\nBy default, every source file that you encrypt/decrypt gets encrypted/decrypted\nto the same directory where the source file is located.\n\n### How can I access Crypter's preferences?\n\nYou can access Crypter's preferences by either clicking on the cog icon in the\nmain Crypter window or by going to `Crypter > Preferences...` from the menu.\n\n### How can I reset my MasterPass?\n\nYou can reset your MasterPass by clicking on the \"Forgot it\" link in the Verify\nMasterPass window. This takes you to a new screen where you can enter a new, valid\nMasterPass. Once you've entered it, click the 'Reset' button and you'll be sent\nback to the verify screen where you can verify your new MasterPass.\n\n### What is a valid MasterPass?\n\nCrypter will not allow you to set an invalid MasterPass. A MasterPass is valid\nwhen it adheres to the following rules:\n\n- It is at least 8 characters long\n- It has at least one uppercase alphabetic character (A-Z)\n- It has at least one lowercase alphabetic character (a-z)\n- It has at least one numeric character (0-9)\n- It has at least one special character (\\$@!%\\*#?&)\n\nThese rules are enforced via the following regular expression:\n\n```javascript\n/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@!%*#?&]).{8,}$/;\n```\n\n### What are MasterPass credentials?\n\nMasterPass credentials are a set of values that are required to derive the\nMasterPassKey from the MasterPass. These values have a pseudo-random element and\nare cryptographically linked. Every MasterPass that is set or reset with Crypter\nhas a unique set of MasterPass credentials. These yield a distinct MasterPassKey,\neven when a MasterPass is reused.\n\n### How can I export my MasterPass credentials?\n\nTo export your MasterPass credentials, you can first open Crypter's\npreferences (see above). From this screen, click on the \"Export\" button. A\ndialog will appear from which you can select the folder where you wish to export\nthe credentials. A success message will confirm a successful export. The\nexported MasterPass credentials file is always named `credentials.crypter`.\n\n### How can I import my MasterPass credentials?\n\nTo import a set of MasterPass credentials, you can first open Crypter's\npreferences (see above). From this screen, click on the \"Import\" button. A\ndialog will appear from which you can locate your `credentials.crypter` file.\nAfter you select it, a success message will confirm a successful import and\nyou will have to verify the MasterPass for the credentials.\n\nNOTE: while Crypter does not require the MasterPass credentials file to be\nexactly named `credentials.crypter`, it does require the file's contents to\nbe unaltered from when it was exported from Crypter. If it has been altered,\nthe import may fail.\n\n<br/>\n\n## Development\n\nCrypter is developed in the \"dev\" branch, which may be unstable at times.\nThis branch should typically be used for pull requests.\n\nThe \"master\" branch will always be kept stable.\n\n### Configurations\n\nAll major configurations that you can apply are found under `app/config.js`.\nThis includes changes to certain cryptography settings. Please be advised\nthat altering these may break functionality and\n[portability](#achieving-portability-and-same-masterpass-reuse).\n\n### Install (dependencies)\n\nTo install all dependencies, run:\n\n```\n$ yarn install\n```\n\n### Run\n\nSince Crypter uses gulp, please install it globally if you have not already\ndone so. To start Crypter, run:\n\n```\n$ gulp\n```\n\n### Test\n\nCrypter primarily uses mocha and chai for testing. Since the project uses a\nlot of JS ES6 syntax, babel is also used as a transpiler. To run all the tests,\nexecute:\n\n```\n$ yarn test\n```\n\nCrypter uses istanbul for coverage. To run test coverage, execute:\n\n```\n$ yarn run coverage\n```\n\n### Build\n\nCrypter's binaries (available under releases) have been built using\nElectron. Since Crypter uses electron-builder to build binaries,\nyou must install it globally:\n\n```\n$ npm install electron-builder@next -g\n```\n\nTo build the app for **macOS**, run:\n\n```\n$ yarn run build:mac\n```\n\nTo build the app for **Linux**, run:\n\n```\n$ sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils\n$ yarn run build:lin\n```\n\nTo build the app for **Windows** x84 and/or x64, run:\n\n```\n$ yarn run build:win\n```\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) Habib Rehman (https://git.io/HR)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished todo so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "script/appveyor-build.cmd",
    "content": "@setlocal enableextensions enabledelayedexpansion\n@echo off\nget-host\nset keyword=build\n\nIf NOT %APPVEYOR_REPO_COMMIT_MESSAGE:build=%==x%APPVEYOR_REPO_COMMIT_MESSAGE% (\n  REM Commit message contains '[build]' so build\n  echo Building Crypter\n  build.cmd\n) else (\n  REM Commit message does not contain '[build]' so just test and skip build\n  echo Testing Crypter\n  test.cmd\n)\nendlocal"
  },
  {
    "path": "script/build.cmd",
    "content": "echo Building Crypter\nREM cd %APPVEYOR_BUILD_FOLDER%\nset NODE_ENV=production\nnpm install electron-builder@next -g\nnpm install --production\nnpm run build:win"
  },
  {
    "path": "script/build_ml.sh",
    "content": "#!/bin/bash\n\necho \"Building for: $TRAVIS_OS_NAME\"\necho \"CWD: $PWD\"\necho \"Node $(node --version)\"\necho \"NPM $(npm --version)\"\n\n# make for production\nexport NODE_ENV=production\nnpm install electron-builder -g\nnpm prune\n\nif [ \"$TRAVIS_OS_NAME\" == \"linux\" ]; then\n  # to build for linux\n  echo \"Building for linux\"\n  docker run --rm \\\n        --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \\\n        -v ${PWD}:/project \\\n        -v ~/.cache/electron:/root/.cache/electron \\\n        -v ~/.cache/electron-builder:/root/.cache/electron-builder \\\n        electronuserland/builder:wine \\\n        /bin/bash -c \"npm i -g electron-builder && yarn run build:lin\"\nelse\n  echo \"Building for mac\"\n  yarn run build:mac\nfi\n# zip -r dist/**/*.zip ./github/RELEASE\n"
  },
  {
    "path": "script/darwin-build.sh",
    "content": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\n\n# remove any existing distribution\nrm -rf dest\n\nnpm install --production\n\nelectron-packager . $npm_package_productName --out=dest --ignore='(test|backups|github)' --asar=false --platform=darwin --arch=x64 --version=$(npm run electronVersion) --icon=res/app-icons/Crypter.icns --app-copyright=Habib_Rehman --overwrite\ncp ./github/RELEASE ./dest/Crypter-darwin-x64/RELEASE\ncp ./license ./dest/Crypter-darwin-x64\nzip -9r ./dest/Crypter-darwin-x64 ./dest/Crypter-darwin-x64\n"
  },
  {
    "path": "script/resolveNodeV.js",
    "content": "const {app} = require('electron')\nconst fs = require('fs-extra')\nconst FILE_PATH = '.nvmrc'\nconsole.log(\"Electron node version is: \"+process.versions.node)\nfs.writeFile(FILE_PATH, process.versions.node, function (err) {\n  if (err) return console.log('Error writing file: ' + err)\n  app.quit()\n})\n"
  },
  {
    "path": "script/test.cmd",
    "content": "node --version\nnpm --version\nREM install all deps\nnpm install --no-optional\nREM run tests\nnpm test"
  },
  {
    "path": "script/travis-build.sh",
    "content": "#!/bin/bash\n\n# set -ev\n# Just exit when fail dont print code to be exec\nset +e\n\nexport TEST_RUN=true\necho \"CC: $CC\"\necho \"CXX: $CXX\"\necho \"OS Name: $TRAVIS_OS_NAME\"\necho \"Node $(node --version)\"\necho \"NPM $(npm --version)\"\n\n# Install deps\nyarn install --ignore-optional\nyarn prune\n\n# Test and get coverage\nyarn run coverage\nyarn run coveralls\n# - npm run codeclimate\n\n# End-to-end OSX testing\n# if [[ \"$TRAVIS_OS_NAME\" == \"osx\" ]]; then\n#   export DISPLAY=:99.0\n#   sh -e /etc/init.d/xvfb start\n#   sleep 3\n#   unset TEST_RUN\n#   npm run xtest\n# fi\n\nunset TEST_RUN\n"
  },
  {
    "path": "script/win-build.sh",
    "content": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\nexport DISPLAY=':99.0'\n\n# remove any existing distribution\nrm -rf dest\n\n# install deps\nsudo apt-get wine\nsudo dpkg --add-architecture i386\nsudo add-apt-repository ppa:wine/wine-builds\nsudo apt-get update\nsudo apt-get install --install-recommends winehq-devel\nsudo apt-get install -y xvfb\n\n# start Xvfb server\nXvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1\n\nnpm install --production\n\n./node_modules/.bin/electron-packager . Crypter --out=dest --ignore='(test|github)' --asar=false --platform=win32 --arch=x64 --version=$(npm run electronVersion) --icon=res/app-icons/Crypter.ico --app-copyright=Habib_Rehman --overwrite\ncp ./github/RELEASE ./dest/Crypter-win32-x64/RELEASE\ncp ./license ./dest/Crypter-win32-x64\nzip -r ./dest/Crypter-win32-x64 ./dest/Crypter-win32-x64\n"
  },
  {
    "path": "test/test.js",
    "content": "'use strict'\nconst assert = require('assert')\nconst path = require('path')\nconst expect = require('chai')\n  .expect\nconst crypto = require('../app/core/crypto.js')\nconst Db = require('../app/core/Db')\nconst MasterPassKey = require('../app/core/MasterPassKey')\nconst MasterPass = require('../app/core/MasterPass')\nconst scrypto = require('crypto')\nconst _ = require('lodash')\nconst fs = require('fs-extra')\nconst logger = require('../app/node_modules/electron-log/src')\nlogger.transports.file.level = false;\nlogger.transports.console.level = false;\n\nconsole.log(`cwd: ${process.cwd()}`)\nconsole.log(`__dirname: ${__dirname}`)\n\nlet checkFileSync = function (path) {\n  try {\n    fs.accessSync(path, fs.F_OK)\n  } catch (err) {\n    if (err.code === 'ENOENT') return false\n  }\n  return true\n}\n\ndescribe(\"Crypter Core Modules' tests\", function () {\n  // Declare globals\n  global.creds = {}\n  global.paths = {\n    mdb: path.join(__dirname, '/tmp/mdb'),\n    tmp: path.join(__dirname, '/tmp')\n  }\n  global.MasterPassKey = new MasterPassKey(scrypto.randomBytes(32))\n\n  // Declare constants\n  const KEY_LENGTH = 32 // 32 bytes\n  const TEST_FOLDER_PATH = `${global.paths.tmp}/test`\n  const TEST_FOLDER_FILE_PATH = `${TEST_FOLDER_PATH}/test.txt`\n  const ENCRYTED_TEST_FOLDER_PATH = `${TEST_FOLDER_PATH}.crypto`\n  const DECRYTING_FOLDER_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FOLDER_PATH)}/.decrypting`\n  const TEST_FILE_PATH = `${global.paths.tmp}/test.txt`\n  const ENCRYTED_TEST_FILE_PATH = `${TEST_FILE_PATH}.crypto`\n  const DECRYTED_TEST_FILE_PATH = `${global.paths.tmp}/test.txt`\n  const TEST_FILE_CONTENTS = '#Crypter'\n  const TEST_CONTENTS_ENCODING = 'utf8'\n  const DECRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.decrypting`\n  const ENCRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.crypting`\n  const DB_TEST_FILE_PATH = `${global.paths.tmp}/db`\n  const BUF2HEX_TEST_ARR = [248, 27, 158, 201, 66, 216, 80, 254, 81, 104, 238, 9, 1, 231, 134, 106, 8, 202, 44, 89, 231, 61, 99, 139, 167, 162, 21, 216, 127, 85, 142, 86]\n  const BUF2HEX_TEST_HEX_EXPECTED = 'f81b9ec942d850fe5168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'\n  const SAFEEQ_TEST_HEX = 'f81b9ec942d850f35168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'\n\n  // Before all tests have run\n  before(() => {\n    // create temporary dir\n    fs.ensureDirSync(global.paths.tmp)\n    process.chdir(global.paths.tmp)\n    console.log(`    > Changed cwd to: ${process.cwd()}`)\n    global.mdb = new Db(global.paths.mdb, function () {})\n  })\n\n  // After all tests have run\n  after(() => {\n    // remove temporary dir\n    fs.removeSync(global.paths.tmp)\n  })\n\n  /**\n   * Crypto module.js\n   ******************************/\n\n  describe('Crypto module', function () {\n    it('should convert string buffer to hex string', function () {\n      const b2hr = crypto.buf2hex(BUF2HEX_TEST_ARR)\n      expect(b2hr)\n        .to.equal(BUF2HEX_TEST_HEX_EXPECTED)\n    })\n    it('should check if two strings are equal time safely', function () {\n      expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, BUF2HEX_TEST_HEX_EXPECTED))\n        .to.be.true\n      expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, SAFEEQ_TEST_HEX))\n        .to.be.false\n      expect(crypto.timingSafeEqual(SAFEEQ_TEST_HEX, BUF2HEX_TEST_HEX_EXPECTED))\n        .to.be.false\n    })\n    describe('deriveKey (and genPassHash)', function () {\n      // deriveKey uses genPassHash Promise internally. Testing the derived\n      // MasterPassKey before and after also suffienctly tests genPassHash\n      // (indirectly test the hash before and after)\n\n      const masterpass = 'crypto#101'\n      it('should deriveKey using a MasterPass correctly when salt is buffer', function () {\n        let mpkey\n        return crypto.deriveKey(masterpass, null)\n          .then((dmp) => {\n            // derived salt should be a Buffer\n            expect(dmp.salt instanceof Buffer)\n              .to.be.true\n            mpkey = dmp.key\n            return crypto.deriveKey(masterpass, dmp.salt)\n          })\n          .then((dmp) => {\n            // The newly derive MPK should equal to the originally derived MPK\n            expect(dmp.key.toString('hex'))\n              .to.equal(mpkey.toString('hex'))\n          })\n      })\n\n      it('should deriveKey using a MasterPass correctly with persistent salt', function () {\n        let mpkey\n        return crypto.deriveKey(masterpass, null)\n          .then((dmp) => {\n            // Serialize and deserialize the derived salt\n            const pdmpsalt = JSON.parse(JSON.stringify(dmp.salt))\n            mpkey = dmp.key\n            // Used deserialized salt to derive key\n            return crypto.deriveKey(masterpass, pdmpsalt)\n          })\n          .then((dmp) => {\n            // The deserialized salt should successfully create a Buffer\n            expect(Buffer.isBuffer(dmp.salt))\n              .to.be.true\n            // The deserialized salt should have been used to recreate the\n            // Buffer orginally used to derive the key correctly which should\n            // result in the newly derive MPK equalling to the originally\n            // derived MPK\n            expect(dmp.key.toString('hex'))\n              .to.equal(mpkey.toString('hex'))\n          })\n      })\n    })\n\n    describe('Encryption', function () {\n      // Before any tests are run in this suite\n      before(function () {\n        // create a test file (for encryption)\n        fs.writeFileSync(TEST_FILE_PATH, TEST_FILE_CONTENTS, TEST_CONTENTS_ENCODING)\n      })\n      after(function () {\n        fs.removeSync(ENCRYTING_TEMP_DIR_PATH)\n      })\n      describe('encrypt promise', function () {\n        it('should encrypt file with pass without errors & have all expected creds', function () {\n          return crypto.encrypt(TEST_FILE_PATH, global.MasterPassKey.get())\n            .then((creds) => {\n              // The encrypted file should exist at the ENCRYTED_TEST_FILE_PATH\n              expect(checkFileSync(ENCRYTED_TEST_FILE_PATH))\n                .to.be.true\n              expect(checkFileSync(ENCRYTING_TEMP_DIR_PATH))\n                .to.be.false\n              // Creds should have all the expected properties\n              expect(creds)\n                .not.be.empty\n              expect(creds.iv)\n                .not.be.empty\n              expect(creds.salt)\n                .not.be.empty\n              expect(creds.key)\n                .not.be.empty\n              expect(creds.tag)\n                .not.be.empty\n            })\n            .catch((err) => {\n              throw err\n            })\n        })\n        it('should throw origin error when empty filepath to encrypt is passed', function () {\n          return crypto.encrypt('', `${global.paths.tmp}`, global.MasterPassKey.get())\n            .catch((err) => {\n              expect(err)\n                .to.be.an('error')\n              expect(err.message)\n                .to.equal(\"ENOENT: no such file or directory, lstat\")\n            })\n        })\n      })\n      describe('crypt promise', function () {\n        // Before any tests are run in this suite\n        before(function () {\n          // create test file\n          fs.removeSync(ENCRYTED_TEST_FILE_PATH)\n        })\n        it('should encrypt file with MPK without errors & have all expected properties', function () {\n          return crypto.crypt(TEST_FILE_PATH, global.MasterPassKey.get())\n            .then((file) => {\n              expect(file)\n                .not.be.empty\n              // The file object should have all the properties\n              expect(file.path)\n                .to.equal(TEST_FILE_PATH)\n              expect(file.cryptPath)\n                .to.equal(ENCRYTED_TEST_FILE_PATH)\n              expect(file.iv)\n                .not.be.empty\n              expect(file.salt)\n                .not.be.empty\n              // Expect key length to be 32 bytes (256 bits)\n              // expect(file.key.length).to.equal(KEY_LENGTH);\n              expect(file.authTag)\n                .not.be.empty\n            })\n        })\n        it('should throw error when MPK not supplied', function () {\n          return crypto.crypt(TEST_FILE_PATH)\n            .catch((err) => {\n              expect(err)\n                .to.be.an('error')\n              expect(err.message)\n                .to.equal('Pass to derive key from not provided')\n            })\n        })\n      })\n    })\n\n    describe('Decryption', function () {\n      // Before any tests are run in this suite\n      before(function () {\n        // create a test file (for encryption)\n        expect(checkFileSync(ENCRYTED_TEST_FILE_PATH))\n          .to.be.true\n      })\n      after(function () {\n        fs.removeSync(DECRYTING_TEMP_DIR_PATH)\n      })\n      describe('decrypt promise', function () {\n        it('should decrypt file with pass without errors & have all expected creds', function () {\n          return crypto.decrypt(ENCRYTED_TEST_FILE_PATH, global.MasterPassKey.get())\n            .then((file) => {\n              // The decrypted file should exist at the ENCRYTED_TEST_FILE_PATH\n              expect(checkFileSync(DECRYTED_TEST_FILE_PATH))\n                .to.be.true\n              // The decrypted file's contents should be the same as the orginals\n              expect(fs\n                  .readFileSync(DECRYTED_TEST_FILE_PATH)\n                  .toString(TEST_CONTENTS_ENCODING))\n                .to.equal(TEST_FILE_CONTENTS)\n              // Creds should have all the expected properties\n              expect(file.op)\n                .to.equal('Decrypted')\n              expect(file.cryptPath)\n                .to.equal(DECRYTED_TEST_FILE_PATH)\n              expect(file.salt)\n                .not.be.empty\n              expect(file.key)\n                .not.be.empty\n              expect(file.authTag)\n                .not.be.empty\n              expect(checkFileSync(DECRYTING_TEMP_DIR_PATH))\n                .to.be.false\n            })\n            .catch((err) => {\n              throw err\n            })\n        })\n        it('should throw origin error when empty filepath to encrypt is passed', function () {\n          return crypto.decrypt('', global.MasterPassKey.get())\n            .catch((err) => {\n              expect(err)\n                .to.be.an('error')\n              expect(err.message)\n                .to.equal(\"ENOENT: no such file or directory, open ''\")\n            })\n        })\n      })\n    })\n\n    describe('Folder', function () {\n      // Before any tests are run in this suite\n      before(function () {\n        // create a test folder with file (for encryption)\n        fs.mkdirSync(TEST_FOLDER_PATH)\n        fs.writeFileSync(TEST_FOLDER_FILE_PATH, TEST_FILE_CONTENTS, TEST_CONTENTS_ENCODING)\n      })\n      after(function () {\n        fs.removeSync(TEST_FOLDER_PATH)\n      })\n      describe('encrypt promise', function () {\n        it('should encrypt folder with pass without errors & have all expected creds', function () {\n          return crypto.encrypt(TEST_FOLDER_PATH, global.MasterPassKey.get())\n            .then((creds) => {\n              // The encrypted folder should exist at the ENCRYTED_TEST_FOLDER_PATH\n              expect(checkFileSync(ENCRYTED_TEST_FOLDER_PATH))\n                .to.be.true\n              // Creds should have all the expected properties\n              expect(creds)\n                .not.be.empty\n              expect(creds.iv)\n                .not.be.empty\n              expect(creds.salt)\n                .not.be.empty\n              expect(creds.key)\n                .not.be.empty\n              expect(creds.tag)\n                .not.be.empty\n            })\n            .catch((err) => {\n              throw err\n            })\n        })\n      })\n      describe('decrypt promise', function () {\n        it('should decrypt folder with pass without errors & have all expected creds', function () {\n          return crypto.decrypt(ENCRYTED_TEST_FOLDER_PATH, global.MasterPassKey.get())\n            .then((folder) => {\n              // The decrypted folder should exist at the ENCRYTED_TEST_FOLDER_PATH\n              expect(checkFileSync(TEST_FOLDER_PATH))\n                .to.be.true\n              // The decrypted folder file should exist at the TEST_FOLDER_FILE_PATH\n              expect(checkFileSync(TEST_FOLDER_FILE_PATH))\n                .to.be.true\n              // The decrypted folder file's contents should be the same as the orginals\n              expect(fs\n                  .readFileSync(TEST_FOLDER_FILE_PATH)\n                  .toString(TEST_CONTENTS_ENCODING))\n                .to.equal(TEST_FILE_CONTENTS)\n              // Creds should have all the expected properties\n              expect(folder.op)\n                .to.equal('Decrypted')\n              expect(folder.cryptPath)\n                .to.equal(TEST_FOLDER_PATH)\n              expect(folder.salt)\n                .not.be.empty\n              expect(folder.key)\n                .not.be.empty\n              expect(folder.authTag)\n                .not.be.empty\n              expect(checkFileSync(DECRYTING_FOLDER_TEMP_DIR_PATH))\n                .to.be.false\n            })\n            .catch((err) => {\n              throw err\n            })\n        })\n      })\n    })\n  })\n  /**\n   * MasterPass module.js\n   ******************************/\n  describe('MasterPass module', function () {\n    after(function () {\n      // close mdb after all tests in this suite\n      global.mdb.close()\n    })\n    it('should set and check masterpass correctly', function () {\n      const pass = 'V1R3$1NNUM3RI$'\n      var mpkey\n      return MasterPass.set(pass)\n        .then((mpk) => {\n          mpkey = mpk\n          return MasterPass.check(pass)\n        })\n        .then((result) => {\n          // Should give match when the correct pass (the one originally set) is\n          // checked\n          expect(result.match)\n            .to.be.true\n          expect(result.key.toString('hex'))\n            .to.equal(mpkey.toString('hex'))\n        })\n    })\n\n    it('should throw error if not provided when checking', function () {\n      const pass = ''\n      return MasterPass.check(pass)\n        .catch((err) => {\n          expect(err)\n            .to.be.an('error')\n          expect(err.message)\n            .to.equal('Pass to derive key from not provided')\n        })\n    })\n\n    it('should throw error if not provided when setting', function () {\n      const pass = ''\n      return MasterPass.set(pass)\n        .catch((err) => {\n          expect(err)\n            .to.be.an('error')\n          expect(err.message)\n            .to.equal('Pass to derive key from not provided')\n        })\n    })\n  })\n\n  /**\n   * MasterPassKey module.js\n   ******************************/\n  describe('MasterPassKey module', function () {\n    it('should get the same masterpasskey that was used to set', function () {\n      const mpkey = scrypto.randomBytes(KEY_LENGTH)\n      const newMPK = scrypto.randomBytes(KEY_LENGTH)\n      const MPK = new MasterPassKey(mpkey)\n      expect(MPK.get())\n        .to.deep.equal(mpkey)\n      expect(MPK.get() instanceof Buffer)\n        .to.be.true\n      MPK.set(newMPK)\n      expect(MPK.get())\n        .to.deep.equal(newMPK)\n    })\n    it('should throw error when deleted and get called', function () {\n      const mpkey = scrypto.randomBytes(KEY_LENGTH)\n      const MPK = new MasterPassKey(mpkey)\n      MPK.delete()\n      expect(MPK.get())\n        .to.be.an('error')\n      expect(MPK.get()\n          .message)\n        .to.equal('MasterPassKey is not set')\n    })\n    it('should throw error when not instantiated with key', function () {\n      const MPK = new MasterPassKey()\n      expect(MPK.get())\n        .to.be.an('error')\n      expect(MPK.get()\n          .message)\n        .to.equal('MasterPassKey is not set')\n    })\n    it('should throw error when instantiated with data type other than a Buffer', function () {\n      const MPK = new MasterPassKey()\n      expect(MPK.set('pass'))\n        .to.be.an('error')\n      expect(MPK.set()\n          .message)\n        .to.equal('MasterPassKey not a Buffer')\n    })\n  })\n\n  /**\n   * Db module.js\n   ******************************/\n  describe('Db module', function () {\n    var db\n    // Helper promises\n    let putValue = function (key, value) {\n      return new Promise(function (resolve, reject) {\n        db.put(key, value, function (err) {\n          if (err) reject(err) // db save error\n          resolve()\n        })\n      })\n    }\n    let close = function (key, value) {\n      return new Promise(function (resolve, reject) {\n        db.put(key, value, function (err) {\n          if (err) reject(err) // db save error\n          resolve()\n        })\n      })\n    }\n    // Before any tests are run in this suite\n    beforeEach((done) => {\n      // open test db\n      db = new Db(DB_TEST_FILE_PATH, function () {\n        done()\n      })\n      // Declare global test object\n      global.testObj = {\n        name: 'crypto',\n        id: 22,\n        secure: true\n      }\n    })\n\n    // After all tests in this suite have run\n    afterEach(function () {\n      // Close test db\n      db.close()\n      fs.removeSync(DB_TEST_FILE_PATH)\n    })\n    it('should save and restore obj correctly', function () {\n      const beforeSaveObj = _.cloneDeep(global.testObj)\n      return db.saveGlobalObj('testObj')\n        .then(() => {\n          global.testObj = null\n          return db.restoreGlobalObj('testObj')\n        })\n        .then(() => {\n          // The object restored from the db should equal to the original object\n          expect(global.testObj)\n            .to.deep.equal(beforeSaveObj)\n        })\n        .catch((err) => {\n          throw (err)\n        })\n    })\n\n    it('should save and restore obj correctly persistently and db.open', function () {\n      // should work still work when db is closed and reopened\n      const beforeSaveObj = _.cloneDeep(global.testObj)\n      return db.saveGlobalObj('testObj')\n        .then(() => {\n          // unset global testObj\n          global.testObj = null\n          expect(db.open)\n            .to.be.true\n          // close db\n          return db.close()\n        })\n        .then(() => {\n          expect(db.open)\n            .to.be.false\n        })\n        .catch((err) => {\n          throw (err)\n        })\n    })\n\n    describe('put and get', () => {\n      it('should resolve value if key exists', function () {\n        // Initialise the database with a value\n        return db.put('key', 'value')\n          .then(() => {\n            return db.get('key')\n          })\n          .then((value) => {\n            // The received value should equal the original put value\n            expect(value)\n              .to.equal('value')\n          })\n          .catch((err) => {\n            throw err\n          })\n      })\n      it('should resolve false if key not found', function () {\n        return db.get('notExist')\n          .then((value) => {\n            expect(value)\n              .to.equal(false)\n          })\n          .catch((err) => {\n            throw err\n          })\n      })\n    })\n\n    describe('saveGlobalObj', () => {\n      it('should not save global object if empty', function () {\n        global.b = {}\n        return db.saveGlobalObj('b')\n          .then(() => {\n            global.b = null\n            return db.restoreGlobalObj('b')\n          })\n          .catch((err) => {\n            // Expect the object not to be found\n            expect(err.notFound)\n              .to.be.true\n            expect(err.status)\n              .to.equal(404)\n            // Expect global.b to not have been restored\n            // i.e. remained null (set after saveGlobalObj)\n            expect(global.b)\n              .to.be.null\n          })\n      })\n      it('should throw error when JSON stringify fails', function () {\n        global.g = {}\n        global.g.a = {\n          b: global.g\n        }\n        // Serializing an (unserializable) object should give error\n        return db.saveGlobalObj('g')\n          .catch((err) => {\n            // Expect an error to occur\n            expect(err)\n              .to.be.an('error')\n            expect(err instanceof TypeError).to.be.true\n          })\n      })\n    })\n\n    describe('restoreGlobalObj', () => {\n      it('should throw error when global object not exist', function () {\n        return db.restoreGlobalObj('fake')\n          .catch((err) => {\n            // Expect an error to occur\n            expect(err.notFound)\n              .to.be.true\n            expect(err.status)\n              .to.equal(404)\n          })\n      })\n\n      it('should throw error when JSON parse fails', function () {\n        return db.put('s', 'i')\n          .then(() => {\n            return db.restoreGlobalObj('s')\n          })\n          .catch((err) => {\n            // Expect an error to occur\n            expect(err)\n              .to.be.an('error')\n          })\n      })\n    })\n  })\n})"
  },
  {
    "path": "test/ui/test.js",
    "content": "'use strict'\nconst Application = require('spectron').Application\nconst assert = require('assert')\nconst chai = require('chai')\nconst fs = require('fs-extra')\nconst expect = chai.expect\nconst chaiAsPromised = require('chai-as-promised')\nconst path = require('path')\nconst logger = require('electron-log')\n\nchai.should()\nchai.use(chaiAsPromised)\n\nconsole.log(`cwd: ${process.cwd()}`)\nconsole.log(`__dirname: ${__dirname}`)\n\nlet wait = function (s) {\n  return new Promise((resolve, reject) => {\n    setTimeout(function () {\n      resolve()\n    }, s)\n  })\n}\n\nlet saveScreenshot = function (client, filename) {\n  return new Promise(function(resolve, reject) {\n    // receive screenshot as Buffer\n    var screenshot = client.saveScreenshot()\n    fs.writeFileSync(filename, screenshot, 'base64')\n    resolve()\n    // fs.writeFile(filename, client.saveScreenshot() , 'base64', function (err) {\n    //   if (err) reject(err)\n    //   resolve()\n    // })\n  })\n}\n\nconst masterpass = 'random#101'\n\nvar responses = {\n  invalid: 'MUST CONTAIN 1 ALPHABET, 1 NUMBER, 1 SYMBOL AND BE AT LEAST 8 CHARACTERS',\n  incorrect: 'INCORRECT MASTERPASS',\n  correct: 'CORRECT MASTERPASS',\n  setSuccess: 'MASTERPASS SUCCESSFULLY SET',\n  empty: 'PLEASE ENTER A MASTERPASS',\n}\n\ndescribe(\"Crypter Render Modules's tests\", function () {\n  this.timeout(10000)\n\n  // before(function () {\n  //   global.paths = {\n  //     mdb: `${this.app.getPath('userData')}/mdb`\n  //   }\n  //\n  //   console.log(require('util').inspect(global.paths, { depth: null }))\n  //   return this.app.start()\n  // })\n\n  beforeEach(function () {\n    this.app = new Application({\n      // path: '../dest/Crypter-darwin-x64/Crypter.app/Contents/MacOS/Electron',\n      path: path.join(__dirname, '../../dest/CrypterTest-darwin-x64/CrypterTest.app/Contents/MacOS/CrypterTest')\n    })\n\n    return this.app.start()\n  })\n\n  afterEach(function () {\n    if (this.app && this.app.isRunning()) {\n      return this.app.stop()\n    }\n  })\n\n  it('should show setup window', function () {\n    return this.app.client.getWindowCount().then(function (count) {\n      assert.equal(count, 1)\n    })\n  })\n\n  describe('Setup', function () {\n    // before(() => {\n    //   // remove /Crypter app dir in userdata before test\n    //   fs.removeSync(``)\n    // })\n\n    it('should give response for masterpass input', function () {\n      const nomp = ''\n      const invalidmp = 'random'\n      let checkResponse = function (client, input, response) {\n        client.setValue('#setMasterPassInput', input)\n          .click('#setMasterPass')\n          .then(() => {\n            return wait(200) // wait 2 seconds\n          })\n          // .then(() => {\n          //   return saveScreenshot(client, `./screens/${response}.png`)\n          // })\n          .getText('#setMasterPassLabel')\n          .then((text) => {\n            expect(text).to.equal(response)\n            return\n          })\n          .catch((err) => {\n            throw err\n          })\n      }\n      return this.app.client\n        .then(() => {\n          return wait(2000) // wait 2 seconds\n        })\n        .click('#getstarted')\n        .then(() => {\n          return wait(1000) // wait 2 seconds\n        })\n        .then(() => {\n          return checkResponse(this.app.client, nomp, responses.empty)\n        })\n        // .then(() => {\n        //   return saveScreenshot(this.app.client, '../../screens/masterpass_empty.png')\n        // })\n        .then(() => {\n          return checkResponse(this.app.client, invalidmp, responses.invalid)\n        })\n        .then(() => {\n          return checkResponse(this.app.client, masterpass, responses.setSuccess)\n        })\n        .then(() => {\n          return wait(2000) // wait 2 seconds\n        })\n        .catch((err) => {\n          throw err\n        })\n    })\n  })\n\n  describe('Main', function () {\n    before(() => {\n    })\n\n    let checkResponse = function (client, input, response) {\n      return new Promise(function(resolve, reject) {\n        return client.setValue('#checkMasterPassInput', input)\n          .click('#checkMasterPass')\n          .then(() => {\n            return wait(1000)\n          })\n          .getText('#checkMasterPassLabel')\n          .then((text) => {\n            expect(text).to.equal(response)\n            resolve()\n          })\n          .catch((err) => {\n            reject(err)\n          })\n      })\n    }\n\n    it('should give response for incorrect masterpass', function () {\n      const mp = 'yolo#10111'\n      return this.app.client\n        .then(() => {\n          return checkResponse(this.app.client, mp, responses.incorrect)\n        })\n    })\n\n    it('should give response for invalid masterpass', function () {\n      const mp = 'yolo'\n      return this.app.client\n        .then(() => {\n          return checkResponse(this.app.client, mp, responses.invalid)\n        })\n    })\n\n    it('should give response for empty masterpass', function () {\n      const mp = ''\n      return this.app.client\n        .then(() => {\n          return checkResponse(this.app.client, mp, responses.empty)\n        })\n    })\n\n    it('should give response for correct masterpass', function () {\n      return this.app.client\n        .then(() => {\n          return checkResponse(this.app.client, masterpass, responses.correct)\n        })\n        .getWindowCount()\n        .then((count) => {\n          console.log(`window count: ${count}`)\n          return\n        })\n    })\n\n    // it('should open file dialog on clicking window', function () {\n    //   return this.app.client\n    //     .then(() => {\n    //       return checkResponse(this.app.client, masterpass, responses.correct)\n    //     })\n    //     .then(() => {\n    //       return wait('5000')\n    //     })\n    //     .click(\"#holder\")\n    // })\n  })\n})\n"
  }
]