Full Code of HR/Crypter for AI

master e2a95eb10feb cached
63 files
208.0 KB
56.4k tokens
40 symbols
1 requests
Download .txt
Showing preview only (223K chars total). Download the full file or copy to clipboard to get everything.
Repository: HR/Crypter
Branch: master
Commit: e2a95eb10feb
Files: 63
Total size: 208.0 KB

Directory structure:
gitextract_a8kqtnjq/

├── .babelrc
├── .codeclimate.yml
├── .github/
│   └── ISSUE_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .travis.yml
├── SECURITY.md
├── app/
│   ├── config.js
│   ├── core/
│   │   ├── Db.js
│   │   ├── MasterPass.js
│   │   ├── MasterPassKey.js
│   │   └── crypto.js
│   ├── index.js
│   ├── package.json
│   ├── src/
│   │   ├── crypter.js
│   │   ├── mainMenu.js
│   │   ├── masterPassPrompt.js
│   │   ├── menu.js
│   │   ├── settings.js
│   │   └── setup.js
│   ├── static/
│   │   ├── crypter.html
│   │   ├── fonts/
│   │   │   └── LICENSE.txt
│   │   ├── js/
│   │   │   ├── common.js
│   │   │   ├── crypter.js
│   │   │   ├── masterpassprompt.js
│   │   │   ├── settings.js
│   │   │   └── setup.js
│   │   ├── masterpassprompt.html
│   │   ├── settings.html
│   │   ├── setup.html
│   │   └── styles/
│   │       ├── crypter.css
│   │       ├── crypter.less
│   │       ├── masterpassprompt.css
│   │       ├── masterpassprompt.less
│   │       ├── mixins.css
│   │       ├── mixins.less
│   │       ├── settings.css
│   │       ├── settings.less
│   │       ├── setup.css
│   │       └── setup.less
│   └── utils/
│       ├── logger.js
│       ├── update.js
│       └── utils.js
├── appveyor.yml
├── build/
│   ├── background.tif
│   ├── crypto.icns
│   ├── icon.icns
│   ├── license.txt
│   └── vicon.icns
├── gulpfile.js
├── license
├── package.json
├── readme.md
├── script/
│   ├── appveyor-build.cmd
│   ├── build.cmd
│   ├── build_ml.sh
│   ├── darwin-build.sh
│   ├── resolveNodeV.js
│   ├── test.cmd
│   ├── travis-build.sh
│   └── win-build.sh
└── test/
    ├── test.js
    └── ui/
        └── test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["@babel/preset-env"]
}

================================================
FILE: .codeclimate.yml
================================================
engines:
  csslint:
    enabled: false
  duplication:
    enabled: true
    config:
      languages:
      - javascript
    exclude_fingerprints:
      - faca6a53a5a4ae580279527e261dd1da
      - 555c80119b06560cd2e96eb4e4e91f26
      - a81ed3d8cb6b36bacb95a0ab40d09d78
      - 9af23dc9a75d6c11f28122e9f83e7309
      - faca6a53a5a4ae580279527e261dd1da
      - fcc5dd7fd381cab75901402345e66511
      - 144b552ca7471e150abb4ece59240e71
      - 63a2186b2e759c9b5f6afb31965ab290
      - 77a83a6a06048f2bda07f8c15c65c9ea
  eslint:
    enabled: true
  fixme:
    enabled: true
ratings:
  paths:
  - "**.css"
  - "**.js"
exclude_paths:
- test/**/*
- "static/js/jquery.marquee.min.js"


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Prerequisites

* [ ] Can you reproduce the problem?
* [ ] Did you read the entire [README.md](https://github.com/HR/Crypter/blob/master/readme.md)?
* [ ] Did you read the [FAQs](https://github.com/HR/Crypter/blob/master/readme.md#faqs)?
* [ ] Do you fully understand how Crypter works and how to use it?
* [ ] Did you check the [issues](https://github.com/HR/Crypter/issues) to see if your bug or enhancement is already reported?

### Description

[Description of the bug or feature]

### Steps to Reproduce

1. [First Step]
2. [Second Step]
3. [and so on...]

**Expected behavior:** [What you expected to happen]

**Actual behavior:** [What actually happened]

### Versions

Crypter:

Release (full release name):

Operating System (name and version):

You 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```.


================================================
FILE: .gitignore
================================================
### OSX ###
.DS_Store
.AppleDouble
.LSOverride

# dev
dest/
.cred
test/specs
test/render.js
.env
spec/
deprecated/
screens/
.crypting/
.decrypting
*.token


# Icon must end with two
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk


### Node ###
# Logs
logs
*.log
npm-debug.log*

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
app/node_modules
node_modules
jspm_packages

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

### Bower ###
bower_components
.bower-cache
.bower-registry
.bower-tmp

# Other stuff to ignore
dist/
*.log
.editorconfig
.Debug
core/elements/*.js
debug
backups/
*.app
chromedriver*

.nyc_output/


================================================
FILE: .nvmrc
================================================
12.14.1

================================================
FILE: .travis.yml
================================================
branches:
  only:
    - master

matrix:
  include:
    - os: linux
      services: docker
      sudo: required
      dist: xenial
      language: generic
      env:
        - ELECTRON_CACHE=$HOME/.cache/electron
        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
    - os: osx
      osx_image: xcode12
      env:
        - ELECTRON_CACHE=$HOME/.cache/electron
        - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
      language: node_js
      node_js:

before_script:
 - chmod +x ./script/*.sh
 - npm install npm -g

script: ./script/travis-build.sh

after_success:
  # Cleanup
  - git reset --hard HEAD
  # Remove untracked and ignored files
  - git clean -dfXn && git clean -dfX
  - chmod +x ./script/*.sh
  # Check if build triggered ([build] in commit message)
  - lgcm=$(git log -1 --pretty=%B | xargs echo);
    if [[ $lgcm == *"[build]"* ]]; then
      ./script/build_ml.sh;
    fi


before_cache:
  - rm -rf $HOME/.cache/electron-builder/wine

cache:
  yarn: true
  apt: true
  directories:
    - node_modules
    - $HOME/.cache/electron
    - $HOME/.cache/electron-builder


notifications:
  email:
    on_success: never
    on_failure: change

addons:
  apt:
    sources:
      - ubuntu-toolchain-r-test
    packages:
      - libgnome-keyring-dev
      - icnsutils
      - xvfb


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

The following versions will receive security updates.

| Version | Supported          |
| ------- | ------------------ |
| 4.x     | :white_check_mark: |
| < 4.0   | :x:                |

## Reporting a Vulnerability

Please report a vulnerbility directly to me at h@rehman.email


================================================
FILE: app/config.js
================================================
'use strict'
/**
 * config.js
 * Provides all essential config constants
 ******************************/

// Fixed constants
const VIEWS_BASE_URI = `file://${__dirname}/static`

module.exports = {
  REPO: {
    URL: 'https://github.com/HR/Crypter/',
    RELEASES_API_URL: 'https://api.github.com/repos/HR/Crypter/releases/latest',
    FORK: 'https://github.com/HR/Crypter/fork',
    DOCS: 'https://github.com/HR/Crypter/blob/master/readme.md',
    REPORT_ISSUE: 'https://github.com/HR/Crypter/issues/new'
  },
  WINDOW_OPTS: {
    center: true,
    show: true,
    titleBarStyle: 'hiddenInset',
    resizable: false,
    maximizable: false,
    movable: true,
    webPreferences: {
      nodeIntegration: true
    }
  },
  VIEWS: {
    BASE_URI: VIEWS_BASE_URI,
    MASTERPASSPROMPT: `${VIEWS_BASE_URI}/masterpassprompt.html`,
    SETUP: `${VIEWS_BASE_URI}/setup.html`,
    CRYPTER: `${VIEWS_BASE_URI}/crypter.html`,
    SETTINGS: `${VIEWS_BASE_URI}/settings.html`
  },
  CRYPTO: {
    ENCRYPTION_TMP_DIR: '.crypting',
    DECRYPTION_TMP_DIR: '.decrypting',
    FILE_DATA: 'data',
    FILE_CREDS: 'creds',
    MASTERPASS_CREDS_FILE: 'credentials.crypter',
    MASTERPASS_CREDS_PROPS: ['mpkhash', 'mpksalt', 'mpsalt'],
    DECRYPT_OP: 'Decrypted',
    DECRYPT_TITLE_PREPEND: 'Decrypted ',
    ENCRYPT_OP: 'Encrypted',
    EXT: '.crypto',
    DEFAULTS: {
      // Crypto default constants
      ITERATIONS: 50000, // file encryption key derivation iterations
      KEYLENGTH: 32, // encryption key length
      IVLENGTH: 12, // initialisation vector length
      ALGORITHM: 'aes-256-gcm', // encryption algorithm
      DIGEST: 'sha256', // digest function
      HASH_ALG: 'sha256', // hashing function
      MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations
    }
  },
  REGEX: {
    APP_EVENT: /^app:[\w-]+$/i,
    ENCRYPTION_CREDS: /^Crypter(.*)$/gim,
    MASTERPASS: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#?&]).{8,}$/
  },
  RESPONSES: {
    invalid:
      'MUST AT LEAST CONTAIN 1 UPPER & LOWER CASE ALPHABET, 1 NUMBER, 1 SYMBOL AND BE 8 CHARACTERS',
    correct: 'CORRECT MASTERPASS',
    incorrect: 'INCORRECT MASTERPASS',
    setSuccess: 'MASTERPASS SUCCESSFULLY SET',
    empty: 'PLEASE ENTER THE MASTERPASS',
    resetSuccess:
      "You have successfully reset your MasterPass. You'll be redirected to verify it shortly.",
    exportSuccess: 'Successfully exported the credentials',
    importSuccess:
      'Successfully imported the credentials. You will need to verify the MasterPass for the credentials imported after Crypter relaunches.'
  },
  ERRORS: {
    INVALID_MP_CREDS_FILE: 'Not a valid or corrupted Crypter credentials file!',
    INVALID_FILE: 'Not a valid or corrupted CRYPTO file!',
    AUTH_FAIL:
      'Corrupted Crypter file or trying to decrypt on a different machine. See git.io/Crypter.info#faqs',
    PROMISE: 'Oops, we encountered a problem...',
    DECRYPT: 'Not a Crypter file (can not get salt, iv and authTag)',
    MS: {
      INVALID_FILE:
        'Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?',
      AUTH_FAIL: 'Unsupported state or unable to authenticate data'
    }
  },
  COLORS: {
    bad: '#dc3545',
    good: '#28a745',
    highlight: '#333333'
  },
  SETTINGS: {
    RELAUNCH_TIMEOUT: 4000
  }
}


================================================
FILE: app/core/Db.js
================================================
'use strict'
/**
 * Db.js
 * Custom JSON DB API implementation
 ******************************/

const _ = require('lodash')
const logger = require('electron-log')
const fs = require('fs-extra')

/**
 * Constructor
 * An alias for the levelup constructor
 * @param {location}:string the location at which the Db to create or is at
 */

// Db class constructor
function Db (path, cb) {
  // If Db already exists at the path, it opens the Db
  // If no Db exists at the path, it creates a new Db

  // Set db path
  this.path = path
  // Save context to instance
  const self = this

  // Create db if does not exist
  fs.ensureFile(path, (err) => {
    if (err) {
      throw err
      return
    }
    // file has now been created or exists
    // either way, read the json now
    fs.readFile(path, (err, db) => {
      if (err) {
        throw err
        return
      }
      if (_.isEmpty(db)) {
        logger.verbose('db is empty so initialising it...')
        self._db = {}
        self.open = true
      } else {
        try {
          logger.verbose('db is not empty so json parsing it...')
          // Get original JSON object or create new if does not exist
          self._db = JSON.parse(db)
          self.open = true
        } catch (err) {
          throw err
          return
        }
      }
      // call callback with newly created object
      cb(self)
    })
  })
}

// Flush db to filesystem
Db.prototype.flush = function () {
  logger.verbose('flushing _db to fs')
  const self = this
  return new Promise((resolve, reject) => {
    fs.outputJson(self.path, self._db, (err) => {
      if (err) reject(err)
      // successfully flushed db to disk
      resolve()
    })
  })
}

// Add items to db
Db.prototype.put = function (key, value) {
  logger.verbose(`Putting ${key} in _db`)
  const self = this
  return new Promise((resolve, reject) => {
    // set value with key in the internal db
    self._db[key] = value
    // then flush db to fs
    self
      .flush()
      .then(() => {
        resolve()
      })
      .catch((err) => {
        reject(err)
      })
  })
}

// Get items from db
Db.prototype.get = function (key) {
  logger.verbose(`Getting ${key} from _db`)
  const self = this
  return new Promise((resolve, reject) => {
    // Return the object if it exists otherwise return false
    resolve(_.has(self._db, key) ? self._db[key] : false)
  })
}

Db.prototype.close = function () {
  logger.verbose(`Closing _db`)
  const self = this
  return new Promise((resolve, reject) => {
    // Check if db is open
    if (self.open) {
      self
        .flush()
        .then(() => {
          self.open = false
          resolve()
        })
        .catch((err) => {
          reject(err)
        })
    }
  })
}

// save a global object of a name (objName)
Db.prototype.saveGlobalObj = function (objName) {
  logger.verbose(`Saving global obj ${objName} to _db`)
  const self = this // get reference to the class instance
  return new Promise((resolve, reject) => {
    // If object not empty then save it in db
    if (!_.isEmpty(global[objName])) {
      // stringify object and store as a string with objName as key
      try {
        // wrap serialization of object around try catch as it could throw error
        const serializedObj = JSON.stringify(global[objName])
        self
          .put(objName, serializedObj)
          .then(() => {
            resolve()
          })
          .catch((err) => {
            reject(err) // db save error
          })
      } catch (err) {
        reject(err)
      }
    } else {
      // If object empty then do not save it
      resolve()
    }
  })
}

// restores a global object of a name (objName)
Db.prototype.restoreGlobalObj = function (objName) {
  logger.verbose(`Restoring global obj ${objName} from _db`)
  const self = this // get reference to th class instance
  return new Promise((resolve, reject) => {
    // get serialized object from db
    self
      .get(objName)
      .then((serializedObj) => {
        try {
          // deserialize object and set as global
          global[objName] = JSON.parse(serializedObj) // try parsing JSON
          resolve()
        } catch (err) {
          // if error occurs while parsing, reject promise
          reject(err)
        }
      })
      .catch((err) => {
        // I/O or other error, pass it up the callback
        reject(err)
      })
  })
}

module.exports = Db


================================================
FILE: app/core/MasterPass.js
================================================
'use strict'
/**
 * MasterPass.js
 * MasterPass functionality
 ******************************/

const crypto = require('./crypto'),
  keytar = require('keytar'),
  SERVICE = 'Crypter',
  ACCOUNT = 'MasterPass'

// TODO: Make independent from global obj! use param instead

exports.init = async () => {
  const masterpass = await keytar.getPassword(SERVICE, ACCOUNT)
  if (!masterpass) return false
  const mpk = await crypto.deriveKey(masterpass, global.creds.mpsalt)
  return mpk
}

exports.save = masterpass => keytar.setPassword(SERVICE, ACCOUNT, masterpass)

// Check MasterPass
exports.check = masterpass => {
  return new Promise((resolve, reject) => {
    // deriveKey using the salt originally used to generate the
    // MasterPassKey
    crypto
      .deriveKey(masterpass, global.creds.mpsalt)
      .then(mpk => {
        // generate the hash for the MasterPassKey
        return crypto.genPassHash(mpk.key, global.creds.mpksalt)
      })
      .then(mpk => {
        // check if MasterPassKey hash is equal to the MasterPassKey hash in mdb
        // Use timingSafeEqual to protect against timing attacks
        const match = crypto.timingSafeEqual(global.creds.mpkhash, mpk.hash)
        // return the match and derived key
        resolve({ match, key: mpk.key })
      })
      .catch(err => {
        reject(err)
      })
  })
}

// Set MasterPass
exports.set = masterpass => {
  return new Promise((resolve, reject) => {
    // Derive the MasterPassKey from the supplied masterpass
    crypto
      .deriveKey(masterpass, null)
      .then(mp => exports.save(masterpass).then(() => mp))
      .then(mp => {
        // Save the salt used to generate the MasterPassKey
        global.creds.mpsalt = mp.salt
        // generate the hash for the MasterPassKey
        return crypto.genPassHash(mp.key, null)
      })
      .then(mpk => {
        // Save the salt used to generate the masterpass
        global.creds.mpkhash = mpk.hash
        global.creds.mpksalt = mpk.salt
        // return the derived mpkey
        resolve(mpk.key)
      })
      .catch(err => {
        // reject if error occurs
        reject(err)
      })
  })
}


================================================
FILE: app/core/MasterPassKey.js
================================================
'use strict'
/**
 * MasterPass.js
 * Provides a way to securely set and retrieve the MasterPass globally
 * MasterPassKey is protected (private var) and only exist in Main memory
 ******************************/

 // Uses closure to securely store MasterPassKey in MasterPassKey object
const MasterPassKey = (function () {
  // Private mpk variable that stores the MasterPassKey
  const mpk = new WeakMap()

  // Class constructor
  function MasterPassKey (key) {
    // Initialise the new instance of class with the MasterPassKey
    mpk.set(this, key)
  }

  // Public get method the for mpk
  MasterPassKey.prototype.get = function () {
    if (mpk.get(this) === undefined) {
      // If MasterPassKey not set or delected
      return new Error('MasterPassKey is not set')
    } else {
      // If MasterPassKey is set then return it
      return mpk.get(this)
    }
  }

  // Public set method the for mpk
  MasterPassKey.prototype.set = function (key) {
    if (key instanceof Buffer) {
      // If the key is a Buffer then set it
      mpk.set(this, key)
    } else {
      // If the key is not a Buffer return an erro
      return new Error('MasterPassKey not a Buffer')
    }
  }

  // Delete the MasterPassKey
  MasterPassKey.prototype.delete = function (key) {
    mpk.delete(this)
  }

  return MasterPassKey
}())

module.exports = MasterPassKey


================================================
FILE: app/core/crypto.js
================================================
'use strict'
/**
 * crypto.js
 * Provides the crypto functionality required
 ******************************/

const fs = require('fs-extra')
const path = require('path')
const scrypto = require('crypto')
const logger = require('electron-log')
const Readable = require('stream').Readable
const tar = require('tar-fs')
const { CRYPTO, REGEX, ERRORS } = require('../config')

// Helper functions

let readFile = path => {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf-8', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}

// Exports

exports.crypt = (origpath, masterpass) => {
  return new Promise((resolve, reject) => {
    logger.verbose(`Encrypting ${origpath}...`)
    // Resolve the destination path for encrypted file
    exports
      .encrypt(origpath, masterpass)
      .then(creds => {
        resolve({
          op: CRYPTO.ENCRYPT_OP, // Crypter operation
          name: path.basename(origpath), // filename
          path: origpath, // path of the (unencrypted) file
          cryptPath: creds.cryptpath, // path of the encrypted file
          salt: creds.salt.toString('hex'), // salt used to derivekey in hex
          key: creds.key.toString('hex'), // dervived key in hex
          iv: creds.iv.toString('hex'), // iv in hex
          authTag: creds.tag.toString('hex') // authTag in hex
        })
      })
      .catch(err => {
        reject(err)
      })
  })
}

exports.encrypt = (origpath, mpkey) => {
  // Encrypts any arbitrary data passed with the pass
  return new Promise((resolve, reject) => {
    // derive the encryption key
    exports
      .deriveKey(mpkey, null, CRYPTO.DEFAULTS.ITERATIONS)
      .then(dcreds => {
        let tag
        let isDirectory = fs.lstatSync(origpath).isDirectory()
        let tempd = `${path.dirname(origpath)}/${CRYPTO.ENCRYPTION_TMP_DIR}`
        let dataDestPath = `${tempd}/data`
        let credsDestPath = `${tempd}/creds`
        logger.verbose(
          `tempd: ${tempd}, dataDestPath: ${dataDestPath}, credsDestPath: ${credsDestPath}, isDirectory: ${isDirectory}`
        )
        // create tempd temporary directory
        fs.mkdirs(tempd, err => {
          if (err) reject(err)
          logger.verbose(`Created ${tempd} successfully`)
          // readstream to read the (unencrypted) file
          const origin = isDirectory
            ? tar.pack(origpath)
            : fs.createReadStream(origpath)
          // create data and creds file
          const dataDest = fs.createWriteStream(dataDestPath)
          const credsDest = fs.createWriteStream(credsDestPath)
          // generate a cryptographically secure random iv
          const iv = scrypto.randomBytes(CRYPTO.DEFAULTS.IVLENGTH)
          // create the AES-256-GCM cipher with iv and derive encryption key
          const cipher = scrypto.createCipheriv(
            CRYPTO.DEFAULTS.ALGORITHM,
            dcreds.key,
            iv
          )

          // Read file, apply tranformation (encryption) to stream and
          // then write stream to filesystem
          origin
            .on('error', err => reject(err))
            .pipe(cipher)
            .on('error', err => reject(err))
            .pipe(dataDest)
            .on('error', err => reject(err))
            .on('finish', () => {
              // get the generated Message Authentication Code
              tag = cipher.getAuthTag()
              // Write credentials used to encrypt in creds file
              const creds = {
                type: 'CRYPTO',
                iv: iv.toString('hex'),
                authTag: tag.toString('hex'),
                salt: dcreds.salt.toString('hex'),
                isDir: isDirectory
              }
              credsDest.end(JSON.stringify(creds))
            })

          // writestream finish handler
          credsDest.on('finish', () => {
            let tarDestPath = origpath + CRYPTO.EXT
            const tarDest = fs.createWriteStream(tarDestPath)
            const tarPack = tar.pack(tempd)
            // Pack directory and zip into a .crypto file
            tarPack
              .on('error', err => reject(err))
              .pipe(tarDest)
              .on('error', err => reject(err))
              .on('finish', () => {
                // Remove temporary dir tempd
                fs.remove(tempd, err => {
                  if (err) reject(err)
                  // return all the credentials and parameters used for encryption
                  logger.verbose('Successfully deleted tempd!')
                  resolve({
                    salt: dcreds.salt,
                    key: dcreds.key,
                    cryptpath: tarDestPath,
                    tag: tag,
                    iv: iv
                  })
                })
              })
          })
        })
      })
      .catch(err => reject(err))
  })
}

exports.decrypt = (origpath, mpkey) => {
  // Decrypts a crypto format file passed with the pass
  return new Promise((resolve, reject) => {
    logger.verbose(`Decrypting ${origpath}...`)
    // Extract a directory
    let tempd = `${path.dirname(origpath)}/${CRYPTO.DECRYPTION_TMP_DIR}`
    let dataOrigPath = `${tempd}/${CRYPTO.FILE_DATA}`
    let credsOrigPath = `${tempd}/${CRYPTO.FILE_CREDS}`
    let dataDestPath = origpath.replace(CRYPTO.EXT, '')
    dataDestPath = dataDestPath.replace(
      path.basename(dataDestPath),
      path.basename(dataDestPath)
    )
    let tarOrig = fs.createReadStream(origpath)
    let tarExtr = tar.extract(tempd)
    // Extract tar to CRYPTO.DECRYPTION_TMP_DIR directory
    tarOrig
      .on('error', err => reject(err))
      .pipe(tarExtr)
      .on('error', err => reject(err))
      .on('finish', () => {
        // Now read creds and use to decrypt data
        logger.verbose('Finished extracting')

        readFile(credsOrigPath)
          .then(credsData => {
            let creds
            try {
              // Try creds v2
              creds = JSON.parse(credsData)
              creds = [creds.iv, creds.authTag, creds.salt, creds.isDir]
            } catch (error) {
              // Try creds v1
              let credsLine = credsData.trim().match(REGEX.ENCRYPTION_CREDS)
              if (!credsLine) {
                return reject(new Error(ERRORS.DECRYPT))
              }
              creds = credsLine[0].split('#').slice(1)
            }

            const iv = Buffer.from(creds[0], 'hex')
            const authTag = Buffer.from(creds[1], 'hex')
            const salt = Buffer.from(creds[2], 'hex')
            const isDir = creds[3]

            logger.verbose(
              `Extracted data, iv: ${iv}, authTag: ${authTag}, salt: ${salt}`
            )
            // Read encrypted data stream
            const dataOrig = fs.createReadStream(dataOrigPath)
            // derive the original encryption key for the file
            exports
              .deriveKey(mpkey, salt, CRYPTO.DEFAULTS.ITERATIONS)
              .then(dcreds => {
                try {
                  let decipher = scrypto.createDecipheriv(
                    CRYPTO.DEFAULTS.ALGORITHM,
                    dcreds.key,
                    iv
                  )
                  decipher.setAuthTag(authTag)
                  let dataDest = isDir
                    ? tar.extract(dataDestPath)
                    : fs.createWriteStream(dataDestPath)

                  dataOrig
                    .on('error', err => reject(err))
                    .pipe(decipher)
                    .on('error', err => reject(err))
                    .pipe(dataDest)
                    .on('error', err => reject(err))
                    .on('finish', () => {
                      logger.verbose(`Encrypted to ${dataDestPath}`)
                      // Now delete tempd (temporary directory)
                      fs.remove(tempd, err => {
                        if (err) reject(err)
                        logger.verbose(`Removed temp dir ${tempd}`)
                        resolve({
                          op: CRYPTO.DECRYPT_OP,
                          name: path.basename(origpath),
                          path: origpath,
                          cryptPath: dataDestPath,
                          salt: salt.toString('hex'),
                          key: dcreds.key.toString('hex'),
                          iv: iv.toString('hex'),
                          authTag: authTag.toString('hex')
                        })
                      })
                    })
                } catch (err) {
                  reject(err)
                }
              })
          })
          .catch(err => {
            reject(err)
          })
      })
  })
}

exports.deriveKey = (pass, psalt) => {
  return new Promise((resolve, reject) => {
    // reject with error if pass not provided
    if (!pass) reject(new Error('Pass to derive key from not provided'))

    // If psalt is provided and is a Buffer then assign it
    // If psalt is provided and is not a Buffer then coerce it and assign it
    // If psalt is not provided then generate a cryptographically secure salt
    // and assign it
    const salt = psalt
      ? Buffer.isBuffer(psalt)
        ? psalt
        : Buffer.from(psalt)
      : scrypto.randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)

    // derive the key using the salt, password and default crypto setup
    scrypto.pbkdf2(
      pass,
      salt,
      CRYPTO.DEFAULTS.MPK_ITERATIONS,
      CRYPTO.DEFAULTS.KEYLENGTH,
      CRYPTO.DEFAULTS.DIGEST,
      (err, key) => {
        if (err) reject(err)
        // return the key and the salt
        resolve({ key, salt })
      }
    )
  })
}

// create a sha256 hash of the MasterPassKey
exports.genPassHash = (masterpass, salt) => {
  return new Promise((resolve, reject) => {
    // convert the masterpass (of type Buffer) to a hex encoded string
    // if it is not already one
    const pass = Buffer.isBuffer(masterpass)
      ? masterpass.toString('hex')
      : masterpass

    // if salt provided then the MasterPass is being checked
    // if salt not provided then the MasterPass is being set
    if (salt) {
      // create hash from the contanation of the pass and salt
      // assign the hex digest of the created hash
      const hash = scrypto
        .createHash(CRYPTO.DEFAULTS.HASH_ALG)
        .update(`${pass}${salt}`)
        .digest('hex')
      resolve({ hash, key: masterpass })
    } else {
      // generate a cryptographically secure salt and use it as the salt
      const salt = scrypto
        .randomBytes(CRYPTO.DEFAULTS.KEYLENGTH)
        .toString('hex')
      // create hash from the contanation of the pass and salt
      // assign the hex digest of the created hash
      const hash = scrypto
        .createHash(CRYPTO.DEFAULTS.HASH_ALG)
        .update(`${pass}${salt}`)
        .digest('hex')
      resolve({ hash, salt, key: masterpass })
    }
  })
}

// Converts a buffer array to a hex string
exports.buf2hex = arr => {
  const buf = Buffer.from(arr)
  return buf.toString('hex')
}

// Compares vars in a constant time (protects against timing attacks)
exports.timingSafeEqual = (a, b) => {
  // convert args to buffers if not already
  a = Buffer.isBuffer(a) ? a : Buffer.from(a)
  b = Buffer.isBuffer(b) ? b : Buffer.from(b)
  var result = 0
  var l = a.length
  while (l--) {
    // bitwise comparison
    result |= a[l] ^ b[l]
  }
  return result === 0
}


================================================
FILE: app/index.js
================================================
'use strict'
/**
 * index.js
 * Entry point for app execution
 ******************************/

const { app, dialog, BrowserWindow } = require('electron'),
  { openNewGitHubIssue, debugInfo } = require('electron-util'),
  debug = require('electron-debug'),
  unhandled = require('electron-unhandled')

unhandled({
  reportButton: error => {
    openNewGitHubIssue({
      user: 'HR',
      repo: 'Crypter',
      body: `\`\`\`\n${error.stack}\n\`\`\`\n\n---\n\n${debugInfo()}`
    })
  }
})
debug()
app.setAppUserModelId('com.github.hr.crypter')

// MasterPass credentials global
global.creds = {}
// User settings global
global.settings = {}
// Paths global (only resolved at runtime)
global.paths = {
  mdb: `${app.getPath('userData')}/mdb`,
  userData: app.getPath('userData'),
  home: app.getPath('home'),
  documents: app.getPath('documents')
}

let fileToCrypt
let settingsWindowNotOpen = true

const logger = require('electron-log')
const { existsSync } = require('fs-extra')
const { checkUpdate } = require('./utils/update')
// Core
const Db = require('./core/Db')
const MasterPass = require('./core/MasterPass')
const MasterPassKey = require('./core/MasterPassKey')
// Windows
const crypter = require('./src/crypter')
const masterPassPrompt = require('./src/masterPassPrompt')
const setup = require('./src/setup')
const settings = require('./src/settings')
const { ERRORS } = require('./config')

// Debug info
logger.info(`Crypter v${app.getVersion()}`)
logger.info(`Process args: ${process.argv}`)
logger.info(`AppPath: ${app.getAppPath()}`)
logger.info(`UseData Path: ${app.getPath('userData')}`)
// Change exec path
process.chdir(app.getAppPath())
logger.info(`Changed cwd to: ${process.cwd()}`)
logger.info(`Electron v${process.versions.electron}`)
logger.info(`Electron node v${process.versions.node}`)
// Prevent second instance
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  app.quit()
}

/**
 * Promisification of initialisation
 **/

const init = function () {
  return new Promise(function (resolve, reject) {
    // initialise mdb
    global.mdb = new Db(global.paths.mdb, function (mdb) {
      // Get the credentials serialized object from mdb
      resolve(mdb.get('creds'))
    })
  })
}

const initMain = function () {
  logger.verbose(`initialising Main...`)
  return global.mdb
    .restoreGlobalObj('creds')
    .then(() => MasterPass.init())
    .then(mpk => mpk && (global.MasterPassKey = new MasterPassKey(mpk.key)))
}

/**
 * Event handlers
 **/

// Main event handler
app.on('ready', function () {
  // Check for updates, silently
  checkUpdate().catch(err => {
    logger.warn(err)
  })
  // Check synchronously whether paths exist
  init()
    .then(mainRun => {
      logger.info(`Init done.`)
      // If the credentials not find in mdb, run setup
      // otherwise run main
      if (mainRun) {
        // Run main
        logger.info(`Main run. Creating CrypterWindow...`)

        // Initialise (open mdb and get creds)
        initMain()
          .then(mpLoaded => {
            logger.verbose(
              'INIT: MasterPass',
              mpLoaded ? 'loaded' : 'not saved'
            )
            // Obtain MasterPass, derive MasterPassKey and set globally
            return mpLoaded || createWindow(masterPassPrompt, false)
          })
          .then(() => {
            // Create the Crypter window and open it
            return createWindow(crypter, fileToCrypt)
          })
          .then(() => {
            // Quit app after crypterWindow is closed
            app.quit()
          })
          .catch(function (error) {
            if (error) {
              // Catch any fatal errors and exit
              logger.error(`PROMISE ERR: ${error.stack}`)
              dialog.showErrorBox(ERRORS.PROMISE, error.message)
            }
            app.quit()
          })
      } else {
        // Run Setup
        logger.info('Setup run. Creating Setup wizard...')

        createWindow(setup)
          .then(() => {
            logger.info('MAIN Setup successfully completed. quitting...')
            // setup successfully completed
            app.quit()
          })
          .catch(function (error) {
            logger.error(`PROMISE ERR: ${error.stack}`)
            // Display error to user
            dialog.showErrorBox(ERRORS.PROMISE, error.message)
            app.quit()
          })
      }
    })
    .catch(function (error) {
      logger.error(`PROMISE ERR: ${error.stack}`)
      // Display error to user
      dialog.showErrorBox(ERRORS.PROMISE, error.message)
      app.quit()
    })
})

/**
 * Electron events
 **/
app.on('will-finish-launching', () => {
  // Check if launched with a file (opened with app in macOS)
  app.on('open-file', (event, file) => {
    if (app.isReady() === false) {
      // Opening when not launched yet
      logger.info('Launching with open-file ' + file)
      fileToCrypt = file
    }
    event.preventDefault()
  })

  // Check if launched with a file (opened with app in Windows)
  if (
    process.argv[1] &&
    process.argv[1].length > 1 &&
    existsSync(process.argv[1])
  ) {
    fileToCrypt = process.argv[1]
  }
})

app.on('window-all-closed', () => {
  logger.verbose('APP: window-all-closed event emitted')
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
})

app.on('quit', () => {
  logger.info('APP: quit event emitted')
  global.mdb.close().catch(err => {
    console.error(err)
    throw err
  })
})

app.on('will-quit', event => {
  // will exit program once exit procedures have been run
  logger.info(`APP.ON('will-quit'): will-quit event emitted`)
  global.mdb.close().catch(err => {
    console.error(err)
    throw err
  })
})

/**
 * Custom events
 **/

app.on('app:quit', () => {
  logger.verbose('APP: app:quit event emitted')
  app.quit()
})

app.on('app:open-settings', () => {
  logger.verbose('APP: app:open-settings event emitted')
  createWindow(settings)
})

app.on('app:check-update', () => {
  logger.verbose('APP: app:check-updates event emitted')
  // Check for updates
  checkUpdate()
    .then(updateAvailable => {
      if (!updateAvailable) {
        dialog.showMessageBox({
          type: 'info',
          message: 'No update available.',
          detail: `You have the latest version Crypter ${app.getVersion()} :)`
        })
      }
    })
    .catch(err => {
      logger.warn(err)
      dialog.showErrorBox(
        'Failed to check for update',
        `An error occured while checking for update:\n ${err.message}`
      )
    })
})

app.on('app:relaunch', () => {
  logger.verbose('APP: app:relaunch event emitted')
  // Relaunch Crypter
  app.relaunch()
  // Exit successfully
  app.quit(0)
  // app.exit(0)
})

app.on('app:reset-masterpass', () => {
  logger.verbose('APP: app:reset-masterpass event emitted')
  createWindow(masterPassPrompt)
})

/**
 * Promisification of windows
 **/

function createWindow (window, ...args) {
  const winInst = BrowserWindow.getAllWindows().find(
    win => win.getTitle() === window.title
  )
  // Focus on existing instance
  if (winInst) return winInst.focus()
  return new Promise((resolve, reject) =>
    window.window(global, ...args, err => {
      if (err) reject(err)
      resolve()
    })
  )
}


================================================
FILE: app/package.json
================================================
{
  "name": "Crypter",
  "productName": "Crypter",
  "version": "5.0.0",
  "description": "An innovative, convenient and secure cross-platform crypto app",
  "license": "MIT",
  "repository": "https://github.com/HR/Crypter",
  "homepage": "https://github.com/HR/Crypter",
  "bugs": "https://github.com/HR/Crypter/issues",
  "private": true,
  "main": "index.js",
  "author": {
    "name": "Habib Rehman",
    "email": "H@Rehman.email",
    "url": "https://git.io/HR"
  },
  "dependencies": {
    "electron-debug": "^3.1.0",
    "electron-log": "^4.2.2",
    "electron-unhandled": "^3.0.2",
    "electron-util": "^0.14.2",
    "estraverse": "5.1.0",
    "fs-extra": "9.0.1",
    "handlebars": "4.7.6",
    "jquery": "3.5.1",
    "jquery.marquee": "1.5.0",
    "keytar": "^6.0.1",
    "lodash": "4.17.19",
    "moment": "2.27.0",
    "normalize.css": "8.0.1",
    "tar-fs": "2.1.0"
  }
}


================================================
FILE: app/src/crypter.js
================================================
const { app, ipcMain, Menu, BrowserWindow } = require('electron')
const { VIEWS, ERRORS, WINDOW_OPTS } = require('../config')
const crypto = require('../core/crypto')
const menuTemplate = require('./mainMenu')
const { isCryptoFile } = require('../utils/utils')
const logger = require('electron-log')
const title = 'Crypter'

exports.title = title

exports.window = function (global, fileToCrypt, callback) {
  // creates a new BrowserWindow
  let win = new BrowserWindow({
    width: 350,
    height: 460,
    title,
    ...WINDOW_OPTS
  })
  // create menu from menuTemplate and set
  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
  // loads crypt.html view into the BrowserWindow
  win.loadURL(VIEWS.CRYPTER)
  let webContents = win.webContents

  webContents.once('did-finish-load', () => {
    // Process any file opened with app before this window
    if (fileToCrypt) {
      logger.info('Got a file to crypt', fileToCrypt)
      cryptFile(fileToCrypt)
    }
  })


  function encrypt(filePath) {
    // Update UI
    webContents.send('encryptingFile', filePath)
    crypto.crypt(filePath, global.MasterPassKey.get())
      .then((file) => {
        webContents.send('cryptedFile', file)
      })
      .catch((err) => {
        logger.info(`cryptFile error`)
        logger.error(err)
        webContents.send('cryptErr', err.message)
      })
  }

  function decrypt(filePath) {
    // Update UI
    webContents.send('decryptingFile', filePath)
    crypto.decrypt(filePath, global.MasterPassKey.get())
      .then((file) => {
        logger.info('decrypted')
        webContents.send('decryptedFile', file)
      })
      .catch((err) => {
        logger.info(`decryptFile error`)
        logger.error(err)
        switch (err.message.trim()) {
        case ERRORS.MS.INVALID_FILE:
          webContents.send('cryptErr', ERRORS.INVALID_FILE)
          break;
        case ERRORS.MS.AUTH_FAIL:
          webContents.send('cryptErr', ERRORS.AUTH_FAIL)
          break;
        default:
          webContents.send('cryptErr', err.message)
        }
      })
  }

  function cryptFile(file) {
    if (isCryptoFile(file)) {
      decrypt(file)
    } else {
      encrypt(file)
    }
  }

  ipcMain.on('app:open-settings', (event) => {
    logger.verbose('CRYPTER: app:open-settings emitted.')
    app.emit('app:open-settings')
  })

  // Process any file opened with app while this window is active
  ipcMain.on('cryptFile', (event, file) => cryptFile(file))

  app.on('open-file', (event, file) => {
    if (app.isReady()) {
      // Opening when already launched
      logger.info('Opening file ' + file)
      cryptFile(file)
    }
    event.preventDefault()
  })

  win.on('closed', function () {
    logger.info('win.closed event emitted for PromptWindow')
    win = null
    callback()
  })

  return win
}

================================================
FILE: app/src/mainMenu.js
================================================
const {app, shell} = require('electron')
const menu = require('./menu')

if (process.platform === 'darwin') {
  menu.unshift({
    label: 'Crypter',
    submenu: [
      { label: 'About Crypter', role: 'about' },
      { label: `Version ${app.getVersion()}`, enabled: false },
      { label: 'Check for Update', click() { app.emit('app:check-update') } },
      { type: 'separator' },
      { label: 'Preferences…', click() { app.emit('app:open-settings') } },
      { type: 'separator' },
      { label: 'Quit', click() { app.emit('app:quit') } }
    ]
  })
}

module.exports = menu


================================================
FILE: app/src/masterPassPrompt.js
================================================
const { ipcMain, Menu, BrowserWindow } = require('electron')
const { VIEWS, WINDOW_OPTS } = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('electron-log')
const menuTemplate = require('./menu')
const title = 'MasterPass'

exports.title = title

exports.window = function (global, resetOnly, callback) {
  let noMP = true // init noMP flag with false
  let error = null
  const CLOSE_TIMEOUT = 2000
  
  // creates a new BrowserWindow
  let win = new BrowserWindow({
    width: 300,
    height: 460,
    title,
    ...WINDOW_OPTS
  })
  // create menu from menuTemplate and set
  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))
  const qs = resetOnly ? '?reset=true' : ''
  // loads masterpassprompt.html view into the BrowserWindow
  win.loadURL(VIEWS.MASTERPASSPROMPT + qs)

  let webContents = win.webContents

  ipcMain.on('checkMasterPass', function (event, masterpass) {
    logger.verbose('IPCMAIN: checkMasterPass emitted. Checking MasterPass...')
    // Check user submitted MasterPass
    MasterPass.check(masterpass)
      .then(res => {
        if (res.match) {
          // Password matches
          logger.info('IPCMAIN: PASSWORD MATCHES!')
          // Save MasterPassKey (while program is running)
          global.MasterPassKey = new MasterPassKey(res.key)
          // Save for next time
          MasterPass.save(masterpass)
          // send result match result to masterpassprompt.html
          webContents.send('checkMasterPassResult', {
            err: null,
            match: res.match
          })
          noMP = false
          // Close after 1 second
          setTimeout(function () {
            // close window (invokes 'closed') event
            win.close()
          }, CLOSE_TIMEOUT)
        } else {
          logger.warn('IPCMAIN: PASSWORD DOES NOT MATCH!')
          webContents.send('checkMasterPassResult', {
            err: null,
            match: res.match
          })
        }
      })
      .catch(err => {
        // Inform user of error (on render side)
        webContents.send('checkMasterPassResult', err.message)
        // set error
        error = err
        // Close after 1 second
        setTimeout(function () {
          // close window (invokes 'closed') event
          win.close()
        }, CLOSE_TIMEOUT)
      })
  })

  ipcMain.on('setMasterPass', function (event, masterpass) {
    // setMasterPass event triggered by render proces
    logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')
    // derive MasterPassKey, genPassHash and set creds globally
    MasterPass.set(masterpass)
      .then(mpkey => {
        // set the derived MasterPassKey globally
        global.MasterPassKey = new MasterPassKey(mpkey)
        return
      })
      .then(() => {
        // save the credentials used to derive the MasterPassKey
        return global.mdb.saveGlobalObj('creds')
      })
      .then(() => {
        // Inform user that the MasterPass has successfully been set
        logger.verbose('IPCMAIN: Masterpass has been reset successfully')
        webContents.send('setMasterPassResult', null)
      })
      .catch(err => {
        // Inform user of the error that occured while setting the MasterPass
        webContents.send('setMasterPassResult', err.message)
        error = err
      })
  })

  win.on('closed', function () {
    logger.info('win.closed event emitted for PromptWindow')
    // send error and noMP back to callee (masterPassPromptWindow Promise)
    if (callback) callback(error || noMP)
    // close window by setting it to nothing (null)
    win = null
  })

  return win
}


================================================
FILE: app/src/menu.js
================================================
const {app, shell} = require('electron')
const {REPO} = require('../config')

module.exports = [
  {
    label: 'Edit',
    submenu: [
      {
        role: 'undo'
      },
      {
        role: 'redo'
      },
      {
        type: 'separator'
      },
      {
        role: 'cut'
      },
      {
        role: 'copy'
      },
      {
        role: 'paste'
      },
      {
        role: 'pasteandmatchstyle'
      },
      {
        role: 'delete'
      },
      {
        role: 'selectall'
      }
    ]
  },
  {
    label: 'Help',
    role: 'help',
    submenu: [
      { label: 'Documentation', click() { shell.openExternal(REPO.DOCS)} },
      { type: 'separator' },
      { label: 'Report Issue', click() { shell.openExternal(REPO.REPORT_ISSUE)} },
      { label: 'Star Crypter', click() { shell.openExternal(REPO.URL)} },
      { label: 'Contribute', click() { shell.openExternal(REPO.FORK)} },
    ]
  }
]


================================================
FILE: app/src/settings.js
================================================
const {app, ipcMain, Menu, BrowserWindow} = require('electron')
const menuTemplate = require('./menu')
const {CRYPTO, VIEWS, SETTINGS, ERRORS, WINDOW_OPTS} = require('../config')
const logger = require('electron-log')
const fs = require('fs-extra')
const title = 'Settings'

exports.title = title
exports.window = function (global, callback) {
  // creates a new BrowserWindow
  let win = new BrowserWindow({
    width: 600,
    height: 460,
    title,
    ...WINDOW_OPTS
  })
  // create menu from menuTemplate and set
  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))

  let webContents = win.webContents
  // loads settings.html view into the BrowserWindow
  win.loadURL(VIEWS.SETTINGS)

  ipcMain.on('export', (event, dir) => {
    logger.verbose(`SETTINGS: export event emitted, got ${dir}`)
    const file = `${dir}/${CRYPTO.MASTERPASS_CREDS_FILE}`
    fs.outputJson(file, global.creds, function (err) {
      if (err) {
        logger.error(err)
        webContents.send('exportResult', err.message)
      } else {
        // Successfully exported
        webContents.send('exportResult', null)
      }
    })
  })

  ipcMain.on('import', (event, file) => {
    logger.verbose(`SETTINGS: import event emitted, got ${file}`)
    fs.readJson(file, global.creds, function (err, credsObj) {
      const invalidCredsErr = new Error(ERRORS.INVALID_MP_CREDS_FILE)
      if (err) {
        logger.error(err)
        webContents.send('importResult', err.message)
      } else {
        let isCredsObjProp = function (prop) {
          return credsObj.hasOwnProperty(prop)
        }
        let credsFileValid = CRYPTO.MASTERPASS_CREDS_PROPS.every(isCredsObjProp)
        logger.verbose(`SETTINGS: Got for credsFileValid ${credsFileValid}`)

        if (credsFileValid) {
          // Is valid (i.e. has required properties)
          // Save in-memory - global creds obj
          global.creds = credsObj
          // Save to fs - via global mdb
          global.mdb.saveGlobalObj('creds')
          // Successfully imported
          webContents.send('importResult', null)
          // Restart after timeout
          setTimeout(function () {
            app.emit('app:relaunch')
          }, SETTINGS.RELAUNCH_TIMEOUT);
        } else {
          webContents.send('importResult', invalidCredsErr.message)
        }

      }
    })
  })

  win.on('closed', function () {
    logger.info('win.closed event emitted for SettingsWindow')
    win = null
    callback()
  })

  return win
}


================================================
FILE: app/src/setup.js
================================================
const {app, ipcMain, Menu, BrowserWindow} = require('electron')
const {VIEWS, WINDOW_OPTS} = require('../config')
const MasterPass = require('../core/MasterPass')
const MasterPassKey = require('../core/MasterPassKey')
const logger = require('electron-log')
const menuTemplate = require('./menu')
const title = 'Setup'

exports.title = title

exports.window = function (global, callback) {
  // setup view controller

  // creates the setup window
  let win = new BrowserWindow({
    width: 600,
    height: 420,
    title,
    ...WINDOW_OPTS
  })

  // create menu from menuTemplate and set
  Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate))

  let webContents = win.webContents
  let error
  // loads setup.html view into the SetupWindow
  win.loadURL(VIEWS.SETUP)

  ipcMain.on('setMasterPass', function (event, masterpass) {
    // setMasterPass event triggered by render proces
    logger.verbose('IPCMAIN: setMasterPass emitted Setting Masterpass...')
    // derive MasterPassKey, genPassHash and set creds globally
    MasterPass.set(masterpass)
      .then((mpkey) => {
        // set the derived MasterPassKey globally
        global.MasterPassKey = new MasterPassKey(mpkey)
        return
      })
      .then(() => {
        // save the credentials used to derive the MasterPassKey
        return global.mdb.saveGlobalObj('creds')
      })
      .then(() => {
        // Inform user that the MasterPass has successfully been set
        webContents.send('setMasterPassResult', null)
      })
      .catch((err) => {
        // Inform user of the error that occured while setting the MasterPass
        logger.error(err)
        webContents.send('setMasterPassResult', err.message)
        error = err
      })
  })

  ipcMain.on('done', function (event, masterpass) {
    // Dond event emotted from render process
    logger.info('IPCMAIN: done emitted setup complete. Closing...')
    // Setup successfully finished
    // therefore set error to nothing
    error = null
    // Relaunch Crypter
    app.emit('app:relaunch')
  })

  win.on('closed', function () {
    logger.verbose('IPCMAIN: win.closed event emitted for setupWindow.')
    // close window by setting it to nothing (null)
    win = null
    // if error occured then send error back to callee else send null
    callback((error) ? error : null)
  })
}

================================================
FILE: app/static/crypter.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="styles/crypter.css" />
    <script src="js/common.js" type="text/javascript"></script>
    <script src="js/crypter.js" type="text/javascript"></script>
  </head>
  <body>
    <section id="crypt">
      <div class="panel-container">
        <div id="panel-crypt" class="current">
          <a class="navigationLink" data-action="app:open-settings">
            <img class="info" src="images/icons/settings.svg" alt="Settings" />
          </a>
          <header>
            <img src="images/icons/Crypter.svg" alt="Crypter" class="header" />
            <h1 class="title">Crypter</h1>
            <p class="subtitle">Encrypt. Decrypt. Anything.</p>
            <p class="leadinfo">
              Encrypt unlimited bits. Remember only a bit.
            </p>
            <p id="errLabel" style="display: none;"></p>
          </header>
          <div id="fileInput" class="fancy">
            <p id="fileInputText">
              Select or Drop
            </p>
          </div>
        </div>
        <div id="panel-crypted">
          <div id="crypted-container">
            <!-- Crypted panel dynamically loaded here -->
          </div>
          <p class="intrfo">
            Note these details down<br />
            (for use with third-party apps)
          </p>
          <footer>
            <a class="back navigationLink" data-panel="crypt">
              <img src="images/icons/back.svg" alt="back" />
            </a>
          </footer>
        </div>
      </div>
    </section>
    <script id="crypted-template" type="text/x-handlebars-template">
      <header>
        <img src="images/icons/{{op}}.svg" class='header' />
      </header>
      <div id='finfo'>
        <h3>
          <div title="{{cryptPath}}">
            {{op}} {{name}}
          </div>
        </h3>
        <table>
          <tr>
            <td>
              Original file
            </td>
            <td class='file-path'>
              <input type='text' readonly='readonly' value="{{path}}" title="{{path}}" />
              <a href='#' onclick="showFile('{{path}}')">
                <img src='images/icons/eye.svg' />
              </a>
            </td>
          </tr>
          <tr>
            <td>
              {{op}} file
            </td>
            <td class='file-path'>
              <input type='text' readonly='readonly' value="{{cryptPath}}" title="{{cryptPath}}" />
              <a href='#' onclick="showFile('{{cryptPath}}')">
                <img src='images/icons/eye.svg' />
              </a>
            </td>
          </tr>
          <tr>
            <td>
              Initialisation Vector
            </td>
            <td>
              <input type='text' readonly='readonly' value="{{iv}}" title="{{iv}}" />
            </td>
          </tr>
          <tr>
            <td>
              Auth Tag
            </td>
            <td>
              <input type='text' readonly='readonly' value="{{authTag}}" title="{{authTag}}" />
            </td>
          </tr>
          <tr>
            <td>
              Salt
            </td>
            <td>
              <input type='text' readonly='readonly' value="{{salt}}" title="{{salt}}" />
            </td>
          </tr>
          <tr>
            <td>
              Encryption Key
            </td>
            <td>
              <input type='text' readonly='readonly' value="{{key}}" title="{{key}}" />
            </td>
          </tr>
        </table>
      </div>
    </script>
  </body>
</html>


================================================
FILE: app/static/fonts/LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: app/static/js/common.js
================================================
'use strict'
/**
 * common.js
 * Contains all the functionality that is common between the views
 ******************************/

/* Common variables */

// Load JQuery library and make accessible via $
window.$ = window.jQuery = require('jquery')
// Cross-view dependencies
const {ipcRenderer, remote, shell} = require('electron')
const { app } = remote
const logger = require('electron-log')
const {REGEX, RESPONSES, COLORS} = require('../config')
const Handlebars = require('handlebars')

/* Shared functions */
function getUrlParameter(name) {
  name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
  var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  var results = regex.exec(location.search);
  return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

function navigate (panel) {
  let oldSel = $('.panel-container > div.current') // get current panel
  let sel = $(`#panel-${panel}`) // get panel to navigate to
  oldSel.removeClass('current') // apply hide styling
  sel.addClass('current') // apply show styling
}

function validateMasterPass(field, errLabel) {
  const MPel = $(`input#${field}Input`)
  const masterpass = MPel.val()
  if (!masterpass) {
    // MP is empty
    // set errLabel text to response for empty and show errLabel
    errLabel.text(RESPONSES.empty).show()
    // Clear MP input field
    MPel.val('')
  } else if (REGEX.MASTERPASS.test(masterpass)) {
    // MP is valid
    // Hide errLabel
    errLabel.hide()
    // Send valid MasterPass to controller function to be checked
    ipcRenderer.send(field, masterpass)
  } else {
    // set errLabel text to response for invalid and show errLabel
    errLabel.text(RESPONSES.invalid).show()
    // Clear MP input field
    MPel.val('')
  }
}

/* Onload */
$(window).on('load', function() {
  $(".navigationLink").each(function(index) {
    let $this = $(this)
    $this.on('click', function(event) {
      let target = $this.data("target")
      let panel = $this.data("panel")
      let tab = $this.data("tab")
      let action = $this.data("action")

      if (action) {
        // Is an action to perform
        if (REGEX.APP_EVENT.test(action)) {
          console.log(`Got main app event ${action}`)
          // Emit event on app
          app.emit(action)
        } else {
          console.log(`Got render event ${action}`)
          // Emit event in render proc
          ipcRenderer.emit(action)
        }
      } else if (tab) {
        console.log(`Got tab ${tab}`)
        // is a tab to navigate to
        $(".item.active").first().removeClass("active")
        $(`a[data-tab='${tab}']`).addClass("active")
        navigate(tab)
      } else if (target) {
        console.log(`Got URL ${target}`)
        // is a URL so open it
        shell.openExternal(target)
      } else if (panel) {
        console.log(`Got panel ${panel}`)
        // target is just a panel to navigate to
        navigate(panel)
      }
      return false
    })
  })
})


================================================
FILE: app/static/js/crypter.js
================================================
'use strict'
/**
 * crypter.js
 * Contains scripts for crypter.html
 ******************************/
const dialog = remote.dialog
const paths = remote.getGlobal('paths')
const { basename } = require('path')
const os = require('os')
let errLabel,
  fileInput,
  fileInputD,
  cryptedContainer,
  fileInputText,
  ifileInputText,
  crypted_template

$(window).on('load', function () {
  // Get DOM elements
  errLabel = $('#errLabel')
  fileInput = $('#fileInput')
  fileInputD = document.getElementById('fileInput')
  cryptedContainer = $('#crypted-container')
  fileInputText = fileInput.find('#fileInputText')
  ifileInputText = fileInputText.text()
  // compile the crypted template
  crypted_template = Handlebars.compile($('#crypted-template').html())
  // attach  event
  fileInputD.ondragover = function () {
    return false
  }
  fileInputD.ondragleave = fileInputD.ondragend = function () {
    return false
  }

  enableFileInput()
})

/* Event listeners */
ipcRenderer.on('cryptedFile', function (event, file) {
  logger.verbose(`IPCRENDER cryptedFile emitted`)
  let fileHTML = crypted_template(file)
  cryptedContainer.html(fileHTML)
  enableUI()
  navigate('crypted')
})

ipcRenderer.on('decryptedFile', function (event, file) {
  logger.verbose(`IPCRENDER decryptedFile emitted`)
  let fileHTML = crypted_template(file)
  cryptedContainer.html(fileHTML)
  enableUI()
  navigate('crypted')
})

ipcRenderer.on('cryptErr', function (event, err) {
  logger.verbose(`IPCRENDER cryptErr emitted`)
  errLabel.text(`ERROR: ${err}`).show()
  enableUI()
})

ipcRenderer.on('encryptingFile', function (event, file) {
  logger.verbose(`IPCRENDER encryptingFile emitted`)
  fileInputText.text(`Encrypting ${basename(file)}...`)
  disableUI()
})

ipcRenderer.on('decryptingFile', function (event, file) {
  logger.verbose(`IPCRENDER decryptingFile emitted`)
  fileInputText.text(`Decrypting ${basename(file)}...`)
  disableUI()
})

/* Helper functions */
function disableFileInput () {
  fileInput.off('click', handler)
  fileInput.ondrop = function () {
    return false
  }
}

function enableFileInput () {
  fileInput.on('click', handler)
  fileInputD.ondrop = function (e) {
    e.preventDefault()
    logger.info(`ONDROP fired!`)
    if (e.dataTransfer.files[0].path) {
      logger.info(`Got file: ${e.dataTransfer.files[0].path}`)
      ipcRenderer.send('cryptFile', e.dataTransfer.files[0].path)
    }
    return false
  }
}

function enableUI () {
  fileInputText.text(ifileInputText)
  enableFileInput()
}

function disableUI () {
  disableFileInput()
  errLabel.hide()
}

function showFile (path) {
  shell.showItemInFolder(path)
}

function showOpenDialog (properties) {
  // Create file input dialog
  dialog
    .showOpenDialog({
      title: 'Choose a file to Encrypt',
      defaultPath: paths.documents, // open dialog at home directory
      properties: properties
    })
    .then(fileData => {
      const filePath = fileData.filePaths
      console.log(filePath)
      // callback for selected file returns undefined if file not selected by user
      if (filePath && filePath.length) {
        // Prevent multiple input dialog
        fileInput.off('click', handler)
        // Select the first one
        ipcRenderer.send('cryptFile', filePath[0])
      } else {
        fileInput.on('click', handler)
      }
    })
}

function handler () {
  if (os.platform() === 'darwin') {
    // macOS allows selecting files and folders so just show dialog
    showOpenDialog(['openFile', 'openDirectory'])
  } else {
    // Windows/Linux only allow selecting either only files or folders so ask the user to choose
    dialog.showMessageBox(
      {
        type: 'question',
        message: 'What would you like to crypt?',
        buttons: ['File', 'Folder', 'Cancel'],
        defaultId: 1
      },
      function (response) {
        switch (response) {
          case 0:
            // File
            showOpenDialog(['openFile'])
            break
          case 1:
            // Folder
            showOpenDialog(['openDirectory'])
            break

          default:
        }
      }
    )
  }

  return false
}


================================================
FILE: app/static/js/masterpassprompt.js
================================================
'use strict'
/**
 * masterpassprompt.js
 * Contains scripts for masterpassprompt.html
 ******************************/

const DONE_TIMEOUT = 2000
let errLabelCheckMP, errLabelSetMP, resetMasterPassInput
const reset = getUrlParameter('reset')

$(window).on('load', function () {
  // Get error label element
  errLabelCheckMP = $('#checkMasterPassLabel')
  errLabelSetMP = $('#setMasterPassLabel')
  resetMasterPassInput = $('#resetMasterPassInput')

  if (reset) {
    navigate('reset')
    $('footer').remove()
  } else {
    navigate('default')
  }
  // hide error label and make red
  errLabelCheckMP.hide().css('color', COLORS.bad)

  $('#checkMasterPass').click(function () {
    validateMasterPass('checkMasterPass', errLabelCheckMP)
  })

  $('#setMasterPass').click(function () {
    validateMasterPass('setMasterPass', errLabelSetMP)
    // Reset validation when reset
    errLabelCheckMP.hide()
  })
})

/* Event listeners */
ipcRenderer.on('setMasterPassResult', function (event, err) {
  if (err) {
    // If error occured
    // Display the error
    errLabelSetMP
      .text(`ERROR: ${err.message}`.toUpperCase())
      .css('color', COLORS.bad)
      .show()
  } else {
    // Display the MasterPass set success
    errLabelSetMP
      .text(RESPONSES.setSuccess)
      .css('color', COLORS.good)
      .show()
    // Change note text to give further instruction and higlight it
    $('p.note')
      .html(RESPONSES.resetSuccess)
      .css('color', COLORS.highlight)

    // Close navigate back to chechMP after 5 seconds
    setTimeout(function () {
      if (reset) {
        return app.emit('app:relaunch')
      }
      navigate('default')
      errLabelSetMP.hide()
    }, DONE_TIMEOUT)
  }
})

ipcRenderer.on('checkMasterPassResult', function (event, result) {
  if (result.err) {
    errLabelCheckMP.text(`ERROR: ${err.message}`).show()
  } else if (result.match) {
    resetMasterPassInput.hide()
    errLabelCheckMP
      .text(RESPONSES.correct)
      .css('color', COLORS.good)
      .show()
  } else {
    errLabelCheckMP.text(RESPONSES.incorrect).show()
  }
})


================================================
FILE: app/static/js/settings.js
================================================
'use strict'
/**
 * settings.js
 * Contains scripts for settings.html
 ******************************/

const dialog = remote.dialog
const creds = remote.getGlobal('creds')
const paths = remote.getGlobal('paths')
const buf2hex = require('../core/crypto').buf2hex
let errLabel
// let settings = remote.getGlobal("settings")

$(window).on('load', function () {
  errLabel = $('#errLabel')
  // Render the credentials
  let creds_temp = Handlebars.compile($('#creds-template').html())
  $('#creds').prepend(
    creds_temp({
      mpsalt: buf2hex(creds.mpsalt),
      mpkhash: creds.mpkhash,
      mpksalt: creds.mpksalt
    })
  )
})

ipcRenderer.on('export', function () {
  dialog
    .showOpenDialog({
      title: 'Choose a dir to export to',
      defaultPath: paths.documents,
      properties: ['openDirectory']
    })
    .then(function (fileData) {
      const dirPath = fileData.filePaths
      // callback for selected file
      // returns undefined if file not selected by user
      if (dirPath && dirPath.length === 1) {
        errLabel.hide()
        console.log(`Got dirpath ${dirPath[0]}`)
        ipcRenderer.send('export', dirPath[0])
      }
    })
})

ipcRenderer.on('exportResult', function (event, err) {
  if (err) {
    errLabel
      .text(`ERROR: ${err.message}`.toUpperCase())
      .css('color', COLORS.bad)
      .show()
  } else {
    errLabel
      .text(RESPONSES.exportSuccess)
      .css('color', COLORS.good)
      .show()
  }
})

ipcRenderer.on('import', function () {
  dialog
    .showOpenDialog({
      title: 'Choose the crypter credentials file',
      defaultPath: paths.documents,
      properties: ['openFile']
    })
    .then(function (fileData) {
      const filePath = fileData.filePaths
      // callback for selected file
      // returns undefined if file not selected by user
      if (filePath && filePath.length === 1) {
        errLabel.hide()
        console.log(`Got filePath ${filePath[0]}`)
        ipcRenderer.send('import', filePath[0])
      }
    })
})

ipcRenderer.on('importResult', function (event, err) {
  window.erro = err
  if (err) {
    console.log(JSON.stringify(err))
    errLabel
      .text(`ERROR: ${err}`.toUpperCase())
      .css('color', COLORS.bad)
      .show()
  } else {
    errLabel
      .text(RESPONSES.importSuccess)
      .css('color', COLORS.good)
      .show()
  }
})


================================================
FILE: app/static/js/setup.js
================================================
'use strict'
/**
 * setup.js
 * Contains scripts for setup.html
 ******************************/

// Delay (in ms) after which it navigates to the done panel
const NAV2DONE_TIMEOUT = 2000
// Delay (in ms) after which the setup window is closed
const DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000
// Animation config
const SPEED = 3000
const OFFSET = SPEED * 1.1
let errLabel

$(window).on('load', function () {
  // Load jQuery marquee plugin
  window.$.marquee = window.jQuery.marquee = require('jquery.marquee')
  // Get error label
  errLabel = $('#setMasterPassLabel')
  // When DOM has loaded... hide error label and make red
  errLabel.hide().css('color', COLORS.bad)

  // attach click event listener to setMasterPass button
  $('#setMasterPass').click(function () {
    // Event handler function
    validateMasterPass('setMasterPass', errLabel)
  })

  $('#done').click(function () {
    // Close setup window and restart app
    ipcRenderer.send('done')
  })

  // navigate to welcome screen by default
  navigate('welcome')

  /* Encryption animation */
  $('.marquee-1').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED}).addClass('visible')
  $('.marquee-2').marquee({direction: 'right', gap: 0, duplicated: true, duration: SPEED, delayBeforeStart: OFFSET})

  setTimeout(function () {
    $('.marquee-2').addClass('visible')
  }, OFFSET)
})

/* Event listeners */
ipcRenderer.on('setMasterPassResult', function (event, err) {
  if (err) {
    // If error occured Display the error
    errLabel.text(`ERROR: ${err.message}`.toUpperCase())
    errLabel.show()
  } else {
    // Display the MasterPass set success
    errLabel.text(RESPONSES.setSuccess).css('color', COLORS.good).show()
    setTimeout(function () {
      // Navigate to the done panel after 2 seconds
      navigate('done')
    }, NAV2DONE_TIMEOUT)

  // 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)
  }
})


================================================
FILE: app/static/masterpassprompt.html
================================================
<!doctype html>
<html>

  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="styles/masterpassprompt.css" charset="utf-8">
    <script src="js/common.js" type="text/javascript"></script>
    <script src="js/masterpassprompt.js" type="text/javascript"></script>
  </head>

  <body>
    <section id="masterpassprompt">
      <img class="header" src="images/icons/masterpass.svg"/>
      <div class="panel-container">
        <div id="panel-default" class="current">
          <header>
            <h1>Verify the MasterPass</h1>
            <p>Please enter your Master Password
            </p>
            <img class="info" src="images/icons/info.svg" alt=""/>
            <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>
          </header>
          <form onsubmit="return false;">
            <div class="masterpass">
              <input type="password" name="masterpass" id="checkMasterPassInput" placeholder="********"/>
              <label for="checkMasterPassInput" id="checkMasterPassLabel"></label>
              <a href="#" id="resetMasterPassInput" class="navigationLink" data-panel="reset">Forgot it?</a>
            </div>
            <button id="checkMasterPass">Verify</button>
          </form>
        </div>
        <div id="panel-reset">
          <header>
            <h1>Reset the MasterPass</h1>
            <p>Please enter a new secure MasterPass
            </p>
            <img class="info" src="images/icons/info.svg"/>
            <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>
          </header>
          <form onsubmit="return false;">
            <div class="masterpass">
              <input type="password" name="name" id="setMasterPassInput" placeholder="********" required/>
              <label for="setMasterPassInput" id="setMasterPassLabel"></label>
            </div>
            <button id="setMasterPass">Reset</button>
          </form>
          <footer>
            <a class="back navigationLink" data-panel="default">
              <img src="images/icons/back.svg"/>
            </a>
          </footer>
        </div>
      </div>
    </section>
  </body>

</html>

================================================
FILE: app/static/settings.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="styles/settings.css" />
    <script src="js/common.js" type="text/javascript"></script>
    <script src="js/settings.js" type="text/javascript"></script>
  </head>

  <body>
    <section id="settings">
      <div class="panel-container">
        <div class="menu">
          <a class="item right navigationLink active" data-tab="general">General</a>
          <a class="item navigationLink" data-tab="contribute">
            <img class="icon" src="images/icons/mark-github.svg" alt="" />
          </a>
        </div>
        <div id="panel-general" class="current">
          <section class="general">
            <header>
              <img class="header" src="images/icons/crypt.svg" alt="" />
              <h3>General</h3>
            </header>
            <section class="category">
              <h4>Credentials</h4>
              <div class="options" id="creds">
                <div class="option">
                  <button class="navigationLink" data-action="export">Export</button>
                  <button class="navigationLink" data-action="import">Import</button>
                </div>
                <div class="option">
                  <button class="navigationLink" data-action="app:reset-masterpass">Reset MasterPass</button>
                </div>
              </div>
              <p id="errLabel" class="info"></p>
            </section>
          </section>
        </div>
        <div id="panel-contribute">
          <section class="contribute">
            <header>
              <img class="header" src="images/icons/mark-github.svg" alt="" />
              <h3>Contribute</h3>
            </header>
            <div class="list">
              <div class="item">
                <a class="navigationLink" data-target="https://github.com/HR/Crypter/issues/new">
                  <img src="images/icons/issue-opened.svg" class="icon" alt="" />
                  <div class="name">
                    Report an issue / Suggest improvements
                  </div>
                </a>
              </div>
              <div class="item">
                <a class="navigationLink" data-target="https://github.com/HR/Crypter">
                  <img src="images/icons/star.svg" alt="" class="icon" />
                  <div class="name">
                    Star the repo
                  </div>
                </a>
              </div>
              <div class="item">
                <a class="navigationLink" data-target="https://github.com/HR/Crypter/fork">
                  <img src="images/icons/repo-forked.svg" alt="" class="icon" />
                  <div class="name">
                    Fork the repo
                  </div>
                </a>
              </div>
              <div class="item">
                <a class="navigationLink" data-action="app:check-update">
                  <img src="images/icons/package.svg" alt="" class="icon" />
                  <div class="name">
                    Check for Updates
                  </div>
                </a>
              </div>
            </div>
          </section>
        </div>
        <footer>
          <img src="images/icons/code.svg" alt="" class="icon" />
          with
          <img src="images/icons/heart.svg" alt="" class="icon" />
          by
          <a class="navigationLink" style="margin-left: 3px" data-target="https://github.com/HR">Habib Rehman</a>
        </footer>
      </div>
    </section>
    <script id="creds-template" type="text/x-handlebars-template">
      <div class='option'>
        <span>
          MasterPass salt
        </span>
        <input class='right' type='text' value="{{mpsalt}}" />
      </div>
      <div class='option'>
        <span>
          MasterPassKey hash
        </span>
        <input class='right' type='text' value="{{mpkhash}}" />
      </div>
      <div class='option'>
        <span>
          MasterPassKey salt
        </span>
        <input class='right' type='text' value="{{mpksalt}}" />
      </div>
    </script>
  </body>
</html>


================================================
FILE: app/static/setup.html
================================================
<!doctype html>
<html>

  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="styles/setup.css" charset="utf-8">
    <script src="js/common.js" type="text/javascript"></script>
    <script src="js/setup.js" type="text/javascript"></script>
  </head>

  <body>
    <section id="setup">
      <div class="panel-container">
        <div id="panel-welcome">
          <header>
            <div class="banner banner-left">
              <div class="marquee marquee-1">
                Information is the resolution of uncertainty.&nbsp;
              </div>
              <div class="marquee marquee-1">
                Rather be without a state than without a voice.&nbsp;
              </div>
              <div class="marquee marquee-1">
                Freedom is never voluntarily given by oppressors.&nbsp;
              </div>
              <div class="marquee marquee-1">
                Everyone has secrets to conceal; Privacy to protect.&nbsp;
              </div>
              <div class="marquee marquee-1">
                Crypto is the ultimate form of non-violent direct action.&nbsp;
              </div>
            </div>
            <div class="banner banner-right">
              <div class="marquee marquee-2">
                aNdtYE1jkAF8i8e6LgqAHNvS5J3Jc9H6XcUlM6YUIDi8rj3a0DLl
              </div>
              <div class="marquee marquee-2">
                zLAgN6Umn61buZC8I6D6+mjZQW2Ap0eXJYDmAi/uGKUQ5ezHI0GI
              </div>
              <div class="marquee marquee-2">
                3j6Nh1XO7WTRFa+1F3i38+TKvdhLt8kS8NAJkS5U670gElWDSffT
              </div>
              <div class="marquee marquee-2">
                yUuJBEfzkIv6t4dcGXSoX4nOh4KFjhrdIymLpnpOlw+7VGLxqWHl
              </div>
              <div class="marquee marquee-2">
                fG2w7C7LKU9eoB4hKjvWJ7EhXPBbzY/+/14VqjCG/O4Kqqr2sWGb
              </div>
            </div>
            <img src="images/icons/Crypter.svg" alt="Crypter Logo" class="himg"/>
            <h1>Welcome to Crypter</h1>
            <p>Encrypt unlimited bits. Remember only a bit.</p>
            <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>
          </header>
          <button class="fancy navigationLink" data-panel="masterpass" id="getstarted">GET STARTED</button>
        </div>

        <div id="panel-masterpass">
          <section id="masterpassprompt">
            <header>
              <div class="banner">
                <img class="himg" src="images/icons/masterpass.svg"/>
              </div>
              <h1>Set a MasterPass for encryption</h1>
              <img class="info" src="images/icons/info.svg" alt=""/>
              <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>
            </header>
            <form onsubmit="return false;">
              <div class="masterpass">
                <input type="password" id="setMasterPassInput" placeholder="********"/>
                <label for="password" id="setMasterPassLabel"></label>
              </div>
              <button class="fancy" id="setMasterPass">SET</button>
            </form>
            <p class="note">
              NOTE: If lost, the MasterPass will be unverifiable! So, please store it safely.
            </p>
          </section>
        </div>

        <div id="panel-done">
          <header>
            <div class="banner">
              <img src="images/icons/done.svg" class="himg"/>
            </div>
            <h1>All done!</h1>
            <p>You have successfully setup Crypter.
              <br/>
              You just need to verify your MasterPass to start Crypting shortly.</p>
          </header>
          <button class="fancy done navigationLink" id="done">FINISH</button>
          <footer>
            <img src="images/icons/code.svg" alt="" class="icon"/>
            with
            <img src="images/icons/heart.svg" alt="" class="icon"/>
            by
            <a class="navigationLink" data-target="https://github.com/HR">Habib Rehman</a>
          </footer>
        </div>
      </div>
    </section>
  </body>

</html>

================================================
FILE: app/static/styles/crypter.css
================================================
@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}

================================================
FILE: app/static/styles/crypter.less
================================================
// out: ./crypter.css, compress: true
/*
	 MasterPassPrompt styles
	 ==========================================================================
*/
@import (less) 'mixins.less';
/* Variable declarations*/
@invalid-color: #9f3a38;
@fileInput-height: 40vh;
/* Section styles */
header {
  margin: 0;
}

header > p {
  color: @blacker;
}

h3 {
  margin-top: 0;
}

#crypt {
  height: 100%;
  min-height: 20rem;
  width: 100%;
  background-color: @white;
  text-align: center;
  overflow: hidden;
}

#crypted-container {
  height: 84vh;
  overflow-y: scroll;
  overflow-x: hidden;
}

.panel-container {
  position: relative;

  & > div {
    position: absolute;
    text-align: center;
    transform: translateX(-110%);
    transition: transform 0.5s;

    & > button {
      margin-top: 1rem;
    }
  }

  & > div.current {
    width: 100%;
    height: 100vh;
    overflow: hidden;
    transform: none;
    position: relative;
  }

  & > div.current ~ div {
    transform: translateX(110%);
  }

  header {
    margin-top: 2.5rem;
  }
}

#panel-crypt {
  display: flex;
  flex-direction: column;
  justify-content: space-between;

  h1 {
    margin: 0.4rem 0;
    font-size: 1.4rem;
  }

  p.subtitle {
    margin-top: 0.6rem;
  }

  p.leadinfo {
    color: @dark;
    font-size: 0.8rem;
    margin: 0.4rem 1rem;
  }

  #fileInput {
    margin-top: 6vh;
    width: 100%;
    height: @fileInput-height;
    text-align: center;
    display: flex;
    flex-flow: column;
    cursor: pointer;
    align-items: center;
    justify-items: center;

    p {
      line-height: @fileInput-height;
      color: @white;
      width: 70%;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
}

#panel-crypted {
  #finfo {
    margin: 1rem;

    h3 > div {
      text-align: center;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }

    table {
      width: 100%;
      font-size: 0.8rem;

      tr {
        margin-top: 0.4rem;

        .bttline {
          vertical-align: inherit;
        }

        .file-path {
          position: relative;

          img {
            height: 16px;
            position: absolute;
            right: 0;
            background-image: linear-gradient(to right, rgba(255,255,255,0.5), rgba(255,255,255,1));
            padding-left: 5px;
          }
        }

        td {
          vertical-align: top;

          input[type='text'] {
            display: table-cell;
            width: 50vw;
            float: right;
            white-space: nowrap;
            text-overflow: ellipsis;
          }

          a > img {
            height: 1rem;
            margin-left: 0.1rem;
          }
        }

        td:first-child {
          text-align: left;
          color: @dark * 0.8;
        }

        td:last-child {
          text-align: left;
          padding-left: 0.2rem;
        }
      }
    }
  }
}

a[data-action='app:open-settings'] {
  img.info {
    height: 1.2rem;
    position: absolute;
    right: 0;
    top: 0;
    padding: 0.5rem;
  }
}

a.back {
  float: left;
}

a.back > img {
  height: 1rem;
  width: auto;
}

footer {
  display: flex;
  clear: both;
  position: absolute;
  border-top: 1px solid @light;
  width: 100%;
  padding-bottom: 0;
  background-color: @white;
  bottom: 0;
  left: 0;
}

footer > a {
  float: right;
  padding: 0.2rem;
}
/* INVALID STYLING */
p#errLabel {
  display: none;
  display: inline-block;
  font-size: 0.8rem;
  padding: 0.2rem;
  color: @invalid-color;
}


================================================
FILE: app/static/styles/masterpassprompt.css
================================================
@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}

================================================
FILE: app/static/styles/masterpassprompt.less
================================================
// out: ./masterpassprompt.css, compress: true
/*
	 MasterPassPrompt styles
	 ==========================================================================
*/
@import (less) "mixins.less";

/* Variable declarations*/
@invalid-color: #9F3A38;

/* Section styles */
header {
  margin: 0;
}
header > p {
  color: @blacker;
}
h1 {
  font-size    : 1.4rem;
  margin-bottom: 0.1rem;
}

button#setMasterPass {
  margin-top: 0.6rem;
  margin-bottom: 0.4rem;
  display: block !important;
}
button#checkMasterPass {
  margin-top: 2rem;
  display: block !important;
}
#masterpassprompt {
  margin: 2rem 0 0 0;
  height          : 100%;
  width           : 100%;
  background-color: @white;
  text-align      : center;
  overflow        : hidden;
}
.panel-container {
  position: relative;
}
.panel-container > div {
  display   : block;
  position  : absolute;
  text-align: center;
  padding   : 0 3rem 3rem;
  transform : translateX(-100%);
  transition: transform 0.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;
  overflow        : hidden;
  padding         : 0 0.5rem;
  transition      : max-height 0.3s, padding 0.3s;
  background-color: #efefef;
  font-size       : 0.8rem;
}
img.info {
  float : right;
  height: 0.9rem;
}
img.info:hover + p.info {
  max-height    : 10rem;
  padding-top   : 0.5rem;
  padding-bottom: 0.5rem;
}
div.masterpass {
  input {
    width: 98%;
  }
  .navigationLink {
    margin-top: 0.2rem;
    float: right;
  }
}

p.note {
  margin-top: 0.2rem;
  font-size: 0.8rem;
}

input[type=password] {
  margin-top   : 1rem;
  border       : none;
  border-bottom: 1px solid @light;
  outline      : none;
  &:focus {
    animation-name    : colorTrans;
    animation-duration: 2s;
    border-bottom     : 1px solid @black;
  }
}
.invalid {
  border-color: @invalid-color !important;
}
div.forgotMP {
  padding   : 0.2rem 0.2rem 0 0;
  text-align: right;
}
#checkMasterPass {
  width: 100% !important;
  margin-top      : 1rem;
}

/* INVALID STYLING */
div.masterpass > label {
  display  : inline-block;
  font-size: 0.8rem;
  margin   : 0.2rem 0;
  color    : @invalid-color;
}


================================================
FILE: app/static/styles/mixins.css
================================================
@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}

================================================
FILE: app/static/styles/mixins.less
================================================
// out: ./mixins.css, compress: true
@import (css) "../../node_modules/normalize.css/normalize.css";

/* theme colors */
@white: #FFFFFF;
@light: #DDDDDD;
@dark: #9D9D9D;
@darker: #555555;
@black: #333333;
@blacker: #222222;
@invalid-color: #F05A5C;
@orange: #EEA849;
@orange_d: #F46B45;
@ic-size: 1.4rem;
@max-height: 100vh;

/* Animations */
@keyframes colorTrans {
  from {
    border-bottom: 1px solid @light;
  }
  to {
    border-bottom: 1px solid @black;
  }
}

/* General (shared) styles */
body {
  font-family     : "Roboto", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, "Lucida Grande", sans-serif;
  font-weight     : 300;
  margin          : 0;
  padding         : 0;
  background-color: @white;
  width           : 100%;
  height          : 100vh;
  overflow        : hidden;
}
p {
  margin: 0;
}
div.none {
  display: none;
}
.left {
  -webkit-align-self: flex-start;
  align-self        : flex-start !important;
}
.right {
  margin-left: auto !important;
}
button, .button {
  background-color: @white;
  border          : 1px solid @light;
  width           : 100%;
  padding         : 0.3rem;
  font-weight     : 400;
  outline         : none;
  &:active,
  &:hover {
    background-color: @light;
  }
}
button.fancy {
  margin-top     : 0.2rem;
  border         : none;
  color          : @white;
  width          : 30% !important;
  background     : linear-gradient(to right, @orange, @orange_d);
  background-size: 200% 200%;
  animation      : OrangeAnimGrad 3s ease infinite;
  &:active,
  &:hover {
    opacity: 0.8;
  }
}
.fancy {
  border         : none;
  color          : @white;
  background     : linear-gradient(to right, @orange, @orange_d);
  background-size: 200% 200%;
  animation      : OrangeAnimGrad 3s ease infinite;
  &:active,
  &:hover {
    opacity: 0.8;
  }
}
input[type=text] {
  border: none;
  border-bottom: 1px solid @light;
  outline: none;
  vertical-align: top;

  &:focus {
    animation-name: colorTrans;
    animation-duration: 2s;
    border-bottom: 1px solid @black;
  }
}
img.header {
  width: 7rem;
  height: auto;
  bottom: 0;
}
img.icon {
  width : @ic-size;
  height: @ic-size;
}
img.info {
  padding-left: 0.2rem;
  height      : 0.8rem;
}
p.intrfo {
  color: @dark;
  font-size: 0.8rem;
  margin: 0.4rem;
}
p.info {
  display         : block;
  width           : 100%;
  box-sizing      : border-box;
  max-height      : 0;
  padding         : 0 0.5rem;
  transition      : max-height 0.5s, padding 0.3s;
  background-color: @light;
  font-size       : 0.8rem;
}
img.info:hover + p.info {
  max-height    : 10rem;
  padding-top   : 0.5rem;
  padding-bottom: 0.5rem;
}
a {
  cursor: pointer;
  cursor: hand;
  font-size      : 0.8rem;
  color          : @blacker;
  text-decoration: none;
  outline        : none;
}
a.back {
  float: left;
}
a.back > img {
  height: 1rem;
  padding: 0.2rem;
  width : auto;
}
footer {
  display: flex;
  position: absolute;
  border-top: 1px solid @light;
  width: 100%;
  bottom: 0;
  left: 0;
  font-size: 0.8rem;
  align-items: center;
  justify-content: left;
  background: @white;
}
/* font declarations */
@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: normal;
  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: bold;
  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: normal;
  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: bold;
  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;
}


================================================
FILE: app/static/styles/settings.css
================================================
@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%}

================================================
FILE: app/static/styles/settings.less
================================================
// out: ./settings.css, compress: true
/* Settings styles
	 ==========================================================================
*/
@import (less) "mixins.less";
/* Variable declarations*/
@list-height: 10rem;
@border-width: 2px;
@spacing04: 0.4rem;
@ic-size: 1.4rem;
@header-ic-size: 6rem;

.panel-container {
  overflow: hidden;
  position: relative;
  height: 100vh;

  & > div:not(.menu) {
    position: absolute;
    text-align: center;
    transform: translateX(-110%);
    transition: transform 0.5s;

    & > button {
      margin-top: 1rem;
    }
  }

  & > div.current {
    width: 100%;
    min-height: 100vh;
    transform: none;
    position: relative;
    display: flex;
    flex-direction: column;

    section {
      height: 90vh;
    }
  }

  & > div.current ~ div {
    transform: translateX(110%);
  }
}

section#settings {
  min-height: 100%;
  position: relative;
  background-color: @white;
}

p#errLabel {
  text-align: center;
}

a:not(.navigationLink) {
  font-size: 0.8rem;
  color: @blacker;
  text-decoration: none;
  outline: none;
}

.left {
  -webkit-align-self: flex-start;
  align-self: flex-start;
}

.right {
  margin-left: auto;
}

.menu {
  display: flex;
  margin-left: 0;
  margin-right: 0;
  align-self: flex-end;
  transition: color 0.1s ease;

  &:after {
    content: '';
    display: block;
    height: 0;
    clear: both;
    visibility: hidden;
  }

  .item:after {
    display: none;
  }

  .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: 0.7rem 0.8rem 0rem;
    margin-right: 0.4rem;
    text-transform: none;
    color: rgba(0, 0, 0, 0.87);
    transition: background 0.1s ease, box-shadow 0.1s ease, color 0.1s ease;
  }

  .active.item {
    border-bottom: @border-width solid rgba(0, 0, 0, 0.8);
    color: rgba(0, 0, 0, 0.95);
    font-weight: normal;
    box-shadow: none;
  }
}

a.item:active {
  border-bottom: @border-width solid rgba(0, 0, 0, 0.1);
  margin-bottom: -@border-width;
}

.active.item {
  background-color: transparent;
  box-shadow: none;
  border-color: @black;
  font-weight: 700;
  margin-bottom: -@border-width;
  color: rgba(0,0,0,.95);
}

.item.right {
  display: flex;
  margin-left: auto !important;
}

img.icon {
  width: @ic-size;
  height: @ic-size;
}
img.header {
  width: 6rem;
  height: auto;
  bottom: 0;
}

h3 {
  margin: 0.5rem 0;
  font-weight: normal;
}

h4 {
  margin-top: 0.5rem;
  margin-bottom: 0.5rem;
  font-weight: 300;
  text-align: left;
  border-bottom: 1px solid @light;
  padding-bottom: 5px;
}

header {
  img.icon {
    width: @header-ic-size;
    height: @header-ic-size;
  }
}

footer {
  display: flex;
  position: absolute;
  border-top: 1px solid @light;
  width: 100%;
  bottom: 0;
  left: 0;
  padding: 0.3rem 0;
  font-size: 0.8rem;
  align-items: center;
  justify-content: center;
  background: @white;

  & > 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: 0.2rem;
  font-size: 0.8rem;
}

div.list {
  height: @list-height;
  width: 100%;
  overflow-y: auto;
}

div.item {
  display: flex;
  align-items: center;
  border-bottom: 1px solid @light;
  padding: @spacing04 0;
  text-align: left;
}
/* Panel styles
* =================
*/
section.general {
  width: 90%;
  margin: auto;
  button {
    margin: 0.4rem 0;
  }
  button:nth-child(1) {
    margin-right: 0.4rem;
  }
}

section.contribute {
  width: 70%;
  margin: auto;

  header {
    border-bottom: 1px solid @light;
  }

  div.list {
    width: 100%;
    margin-bottom: 3rem;
    height: 100%;

    .item {
      a {
        display: flex;
        align-items: center;
        text-align: left;
      }

      .name {
        font-weight: 300;
      }

      .icon {
        padding: 0.4rem;
      }
    }
  }
}

section.crypto {
  width: 90%;
  margin: 1rem auto auto;

  header > img {
    height: 4rem !important;
    width: auto;
  }

  button {
    width: 20%;
  }
}

================================================
FILE: app/static/styles/setup.css
================================================
@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}

================================================
FILE: app/static/styles/setup.less
================================================
// out: ./setup.css, compress: true
/* Setup styles
	 ==========================================================================
*/
@import (less) "mixins.less";
/* Variable declarations*/
@border-width: 2px;
@side-margin: 0.8rem;
@section-width: 60%;
@spacing04: 0.4rem;
@spacing-ic-txt: @side-margin - 0.4rem;
/* TODO: ADD MARGIN RELATIVELY/CONTEXTUALLY TO CORE ELEMENTS I.E. for a button, use panel-container > button*/
/* Animations */
@keyframes colorTrans {
  from {
    background-color: @white;
  }
  to {
    background-color: @light;
  }
}
@-webkit-keyframes OrangeAnimGrad {
  0% {
    background-position: 0 51%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0 51%;
  }
}
@keyframes OrangeAnimGrad {
  0% {
    background-position: 0 51%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0 51%;
  }
}
/* General styles */
#setup {
  height    : 100%;
  width     : 100%;
  text-align: center;
  overflow  : hidden;
}
form {
  margin: 0;
}
header {
  margin: 0;
  p {
    color: @blacker;
  }
  .minimal {
    h1 {
      margin-top : 1rem;
      font-weight: 300;
    }
  }
}
h1 {
  font-size    : 1.4rem;
  margin-bottom: 0.4rem;
  font-weight  : 400;
}
p {
  margin: 0;
  color : @dark;
}
h1 + p {
  margin: 0 2rem;
}
.panel-container {
  position: relative;
  & > div {
    position  : absolute;
    text-align: center;
    transform : translateX(-110%);
    transition: transform 0.5s;
    & > button {
      display: none;
    }
  }
  & > div.current {
		width : 100%;
		height: 100vh;
    overflow : hidden;
    transform: none;
    position : relative;
    & > button {
      margin-top: 1rem;
      display: inline-block;
    }
  }
  & > div.current ~ div {
    transform: translateX(110%);
  }
  header {
    padding-top: 2rem;
  }
}

// .panel-container > div {
// 	position: absolute;
// 	text-align: center;
// 	transform: translateX(-110%);
// 	transition: transform 0.5s;
// }
// .panel-container > div > footer {
// 	transform: translateX(-110%);
// }
//
// .panel-container > div.current > footer {
// 	transform: translateX(100%);
// }
/* TODO DISABLE ANIMATION ON FIRST PANE */
// #panel-welcome ~ div {
// 	transform: none;
// }
// #panel-welcome > div {
// 	transform: none;
// 	transition: none;
// }
.right {
  margin-left: auto !important;
}
h3 {
  margin     : 0.5rem 0;
  font-weight: normal;
}
a.csp {
  padding: 0.6rem 0 !important;
  img {
    height: 1.6rem;
    widht : auto;
  }
}
footer {
  display         : flex;
  clear           : both;
  position        : absolute;
  border-top      : 1px solid @light;
  width           : 100%;
  padding-bottom  : 0;
  background-color: @white;
  bottom          : 0;
  left            : 0;
}
footer > a {
  float  : right;
  padding: 0.2rem;
}
.himg {
  width  : 9rem;
  height : auto;
  z-index: 1000;
}
div.list {
  width   : 100%;
  overflow: auto;
}
div.item > a {
  display      : flex;
  align-items  : center;
  border-bottom: 1px solid @light;
  padding      : @spacing04 0;
  text-align   : left;
}
img.info {
  float : right;
  height: 0.9rem;
}
p.info {
  display         : block;
  width           : 100%;
  box-sizing      : border-box;
  max-height      : 0;
  overflow        : hidden;
  padding         : 0 0.5rem;
  transition      : max-height 0.3s, padding 0.3s;
  background-color: #efefef;
  font-size       : 0.8rem;
}
input {
  width: 98%;
}
input[type=password] {
  margin-top   : 1rem;
  border       : none;
  border-bottom: 1px solid @light;
  outline      : none;
  &:focus {
    border-bottom: 1px solid @black;
  }
}
input[type=submit] {
  margin-top      : 1rem;
  height          : 2rem;
  font-weight     : normal;
  background-color: @light;
  border          : 1px solid @light;
  width           : 100%;
  outline         : none;
  &:focus {
    border: 1px solid @black;
  }
}
div.submit {
  text-align      : right;
  background-color: @white;
  width           : 100%;
}
.none {
  display: none;
}

/* Panel styles
* =================
*/
.banner {
  margin         : 1rem auto 0;
  position       : relative;
  align-items    : center;
  justify-content: center;
  width          : 100%;
}
.banner-left {
  margin-left: -3rem;
  float      : left;
}
.banner-right {
  margin-right: -3rem;
  float       : right;
}
/* Welcome Panel styles
* =================
*/
#panel-welcome {
  .marquee {
    overflow: hidden;
    position: relative;
  }
  .marquee-1 {
    visibility: hidden;
  }
  .marquee-2 {
    visibility: hidden;
  }
  .marquee.visible {
    visibility: visible;
  }
  & > header {
    .banner {
      margin  : 2rem auto 0;
      position: relative;
      display : inline-block;
      width   : 50%;
      z-index : -1;
      color   : @light;
    }
    .banner + .himg {
      margin-top: -7.5rem !important;
    }
    .himg {
      z-index: 1000;
    }
  }
}
/* Done Panel styles
* =================
*/
#panel-done {
  footer {
    padding: 0.3rem 0;
    text-align     : center;
    display        : flex;
    align-items    : center;
    font-size      : 0.8rem;
    justify-content: center;
    img {
      height: 1rem !important;
    }
  }
}

/* MasterPass Panel styles
* =================
*/
#masterpassprompt {
  height    : 100%;
  min-height: 20rem;
  width     : @section-width;
  margin    : auto;
}
#setMasterPass {
  margin     : 0.5rem 0;
  font-weight: normal;
  width      : 100%;
}
#masterpassprompt p.note {
  margin   : none;
  font-size: 0.8rem;
}
.forgotMP {
  padding   : 0.2rem 0.2rem 0 0;
  text-align: right;
}

/* INVALID STYLING */
.masterpass > label {
  display  : inline-block;
  font-size: 0.8rem;
  margin   : 0.2rem 0;
  color    : @invalid-color;
}
.invalid {
  border-color: @invalid-color !important;
}


================================================
FILE: app/utils/logger.js
================================================
'use strict'
/**
 * logger.js
 * Custom logger for debugging
 ******************************/
const { createLogger, format, transports } = require('winston')
const { isRenderer } = require('./utils')

// const { app } = require('electron')
if (process.env.TEST_RUN || isRenderer()) {
  module.exports = createLogger({
    silent: true,
    exitOnError: false
  })
} else {
  const moment = require('moment')
  const fs = require('fs-extra')
  // const IN_DEV = !app.isPackaged
  let debugDir = `${global.paths.userData}/debug`
  fs.ensureDirSync(debugDir)
  const fileTransport = new transports.File({
    filename: `${debugDir}/CS_debug_${moment().format('DD.MM@HH:MM').trim()}.log`,
    handleExceptions: true,
    colorize: false,
    level: 'verbose'
  })

  const logFormat = format.combine(
    format.colorize(),
    format.timestamp(),
    format.prettyPrint(),
    format.align(),
    format.splat(),
    format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
  )

  const logger = createLogger({
    transports: [
      new transports.Console(),
      fileTransport
    ],
    exitOnError: false,
    format: logFormat,
    level: 'verbose'
  })

  module.exports = logger
}

================================================
FILE: app/utils/update.js
================================================
const https = require('https')
const { app, dialog, shell } = require('electron')
const { REPO } = require('../config')
const USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub'
const VERSION_REGEX = /[\.v]+/g
const VERSION = parseV(app.getVersion())

function parseV(str) {
  return parseInt(str.replace(VERSION_REGEX, ''))
}

module.exports = {
  checkUpdate: function () {
    return new Promise((resolve, reject) => {
      https.get(REPO.RELEASES_API_URL, {
          headers: { 'User-Agent': USER_AGENT }
        }, (res) => {
          let data = ''

          res.on('data', (chunk) => {
            data += chunk
          })

          res.on('end', () => {
            try {
              release = JSON.parse(data.toString('utf8'))
              const LATEST_VERSION = parseV(release.tag_name)
              if (VERSION < LATEST_VERSION) {
                dialog.showMessageBox({
                  type: 'info',
                  message: `Update is available.`,
                  detail: `A new version Crypter ${release.tag_name} is available.\nDo you want to get it?`,
                  buttons: ['Get update', 'Later'],
                  defaultId: 0,
                  cancelId: 1,
                  icon: null
                }, (response) => {
                  if (response === 0) {
                    // Update button pressed
                    shell.openExternal(release.html_url)
                  }
                })
                resolve(true)
              } else {
                resolve(false)
              }
            } catch (err) {
              reject(err)
            }
          })
        })
        .on('error', (err) => reject(err))
    })
  }

}

================================================
FILE: app/utils/utils.js
================================================
const { extname } = require('path')
const { CRYPTO } = require('../config')

module.exports = {
  isRenderer: () => {
    // running in a web browser
    if (typeof process === 'undefined') return true

    // node-integration is disabled
    if (!process) return true

    // We're in node.js somehow
    if (!process.type) return false

    return process.type === 'renderer'
  },
  isCryptoFile: (file) => extname(file).toLowerCase() === CRYPTO.EXT
}

================================================
FILE: appveyor.yml
================================================
version: "{build}"

platform:
  - x64

image: Visual Studio 2019


cache:
  - node_modules
  - app\node_modules
  - '%APPDATA%\npm-cache'
  - '%USERPROFILE%\.electron'

init:
  - git config --global core.autocrlf input

# Only build if commit message contains '[build]'
only_commits:
  message: /\[build\]/

install:
  - set /p NODE_VERSION=<.nvmrc
  - ps: Install-Product node $env:NODE_VERSION $env:PLATFORM
  - npm install npm -g
  - npm install electron-builder@next -g
  - npm install --production
  - npm prune

build_script:
  - node --version
  - npm --version
  - npm run build:win

# Post-install test scripts.
# test_script:
#   - script\test.cmd
test: off

================================================
FILE: build/license.txt
================================================
The MIT License (MIT)

Copyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished TODO so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: gulpfile.js
================================================
const gulp = require('gulp')
const shell = require('child_process').exec
const spawn = require('child_process').spawn
const babel = require('gulp-babel')
const env = require('gulp-env')
const less = require('gulp-less')
const path = require('path')
const istanbul = require('gulp-babel-istanbul')
const injectModules = require('gulp-inject-modules')
const mocha = require('gulp-mocha')
const LessPluginCleanCSS = require('less-plugin-clean-css')
const cleancss = new LessPluginCleanCSS({ advanced: true })
const LESS_FILES = './app/static/styles/*.less'
const ELECTRON = __dirname + '/node_modules/.bin/electron'
const DEBUG = false
let p
let args = ['.']
// Start the electron process.
async function electron () {
  // kill previous spawned process
  if (p) {
    p.kill()
  }

  if (DEBUG) args.unshift('--inspect=5858')
  // `spawn` a child `gulp` process linked to the parent `stdio`
  p = await spawn(ELECTRON, args, {
    stdio: 'inherit',
    env: {
      ...process.env
    }
  })
}

gulp.task('run', electron)

gulp.task('watch', function (done) {
  gulp.watch(LESS_FILES, gulp.series('less'))
  gulp.watch(['./app/*.js', './app/src/*.js', './app/core/*.js'], gulp.series('run'))
  done()
})

gulp.task('nodev', function () {
  return shell(
    // start electron main and render process
    `node_modules/.bin/electron script/resolveNodeV.js`,
    function (err, stdout, stderr) {
      console.log(stdout)
      console.log(stderr)
    }
  )
})

gulp.task('less', function () {
  return gulp
    .src('./app/static/styles/*.less')
    .pipe(
      less({
        paths: [path.join(__dirname, 'less', 'includes')],
        plugins: [cleancss]
      })
    )
    .pipe(gulp.dest('./app/static/styles/'))
})

/* TEST */

gulp.task('test', () => {
  shell(
    // Run test stuff
    'mocha --require babel-core/register test/'
  )
})

gulp.task('coverage', function (cb) {
  const envs = env.set({
    TEST_RUN: true
  })
  gulp
    .src('./app/core/**/*.js')
    .pipe(envs)
    .pipe(istanbul())
    .pipe(istanbul.hookRequire())
    .on('finish', function () {
      gulp
        .src('test/*.js')
        .pipe(babel())
        .pipe(injectModules())
        .pipe(mocha())
        .pipe(istanbul.writeReports())
        .on('end', cb)
    })
})

/* BUILD */

gulp.task('rebuildni', () => {
  shell(
    // start node inspector server
    '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'
  )
})

gulp.task('buildnative', () => {
  shell(
    // start build the native module
    './node_modules/.bin/electron-rebuild #1'
  )
})

gulp.task('ni', () => {
  shell(
    // start node inspector server
    'ELECTRON_RUN_AS_NODE=true node_modules/.bin/electron node_modules/node-inspector/bin/inspector.js'
  )
})

gulp.task('driver', () => {
  shell(
    // Run chromedriver
    './node_modules/chromedriver/bin/chromedriver'
  )
})

gulp.task('default', gulp.series('less', 'watch', 'run'))


================================================
FILE: license
================================================
The MIT License (MIT)

Copyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished TODO so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: package.json
================================================
{
  "name": "Crypter",
  "productName": "Crypter",
  "version": "5.0.0",
  "description": "An innovative, convenient and secure cross-platform crypto app",
  "license": "MIT",
  "repository": "https://github.com/HR/Crypter",
  "homepage": "https://github.com/HR/Crypter",
  "bugs": "https://github.com/HR/Crypter/issues",
  "main": "./app/index.js",
  "author": {
    "name": "Habib Rehman",
    "email": "H@Rehman.email",
    "url": "https://git.io/HR"
  },
  "build": {
    "appId": "com.github.hr.crypter",
    "asar": false,
    "files": [
      "**/*",
      "!**/node_modules/.bin",
      "!**/._*",
      "!**/{.*,.git,*.yml,*.md,README,readme,test*,*.less,gulpfile.js}",
      "!script${/*}",
      "!test${/*}"
    ],
    "compression": "normal",
    "fileAssociations": {
      "ext": "crypto",
      "name": "CRYPTO",
      "role": "Editor",
      "description": "The Crypter encryption format. More info at git.io/Crypter.info#crypto-file"
    },
    "mac": {
      "category": "public.app-category.utilities",
      "publish": "github",
      "identity": null
    },
    "dmg": {
      "background": "build/background.tif",
      "icon": "vicon.icns",
      "iconSize": 116,
      "iconTextSize": 13,
      "contents": [
        {
          "x": 240,
          "y": 135
        },
        {
          "x": 240,
          "y": 400,
          "type": "link",
          "path": "/Applications"
        }
      ],
      "window": {
        "width": "480",
        "height": "540"
      }
    },
    "linux": {
      "category": "Utility",
      "publish": "github"
    },
    "win": {
      "icon": "icon.ico",
      "target": "nsis",
      "publish": "github"
    },
    "nsis": {
      "oneClick": true,
      "perMachine": true
    },
    "snap": {
      "publish": "github"
    }
  },
  "scripts": {
    "pack": "electron-builder --dir",
    "build:mac": "electron-builder -m -c.electronVersion $(npm info electron version)",
    "build:lin": "electron-builder -l --x64 --ia32 -c.electronVersion $(npm info electron version)",
    "build:win": "electron-builder -w --x64 --ia32",
    "start": "electron .",
    "postinstall": "electron-builder install-app-deps",
    "nodeGypReBuild": "electron-rebuild .",
    "electronV": "electron -v | sed s/\\v//g",
    "test": "TEST_RUN=true mocha --require @babel/register test/test.js",
    "coverage": "TEST_RUN=true nyc --reporter=lcov mocha -- test/test.js",
    "coveralls": "coveralls < coverage/lcov.info",
    "codeclimate": "node codeclimate-test-reporter < coverage/lcov.info",
    "package": "electron-packager . $npm_package_productName --out=dest --ignore='(test|dest|coverage)' --prune --asar --all --version=$(npm run electronVersion)",
    "winpackage": "chmod +x script/win-build.sh && script/win-build.sh",
    "xtest": "npm run xtestbuild && npm run xltest",
    "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",
    "xltest": "unset TEST_RUN && rm -rf ~/Library/Application\\ Support/CrypterTest/ && mocha --require @babel/register ./test/ui/*.js",
    "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"
  },
  "keywords": [
    "encryption",
    "decryption",
    "crypto",
    "end-to-end",
    "client",
    "electron"
  ],
  "devDependencies": {
    "@babel/core": "^7.11.0",
    "@babel/preset-env": "^7.11.0",
    "@babel/register": "^7.10.5",
    "babel-cli": "6.26.0",
    "chai": "4.2.0",
    "chai-as-promised": "7.1.1",
    "codeclimate-test-reporter": "0.5.1",
    "coveralls": "3.1.0",
    "electron": "9.1.2",
    "electron-builder": "^22.8.0",
    "electron-packager": "15.0.0",
    "electron-rebuild": "1.11.0",
    "gulp": "4.0.2",
    "gulp-babel": "8.0.0",
    "gulp-babel-istanbul": "1.6.0",
    "gulp-env": "0.4.0",
    "gulp-inject-modules": "1.0.0",
    "gulp-json-editor": "2.5.4",
    "gulp-less": "4.0.1",
    "gulp-mocha": "7.0.2",
    "isparta": "4.1.1",
    "less-plugin-clean-css": "1.5.1",
    "mocha": "8.1.0",
    "mocha-lcov-reporter": "1.3.0",
    "nyc": "^15.1.0",
    "spectron": "11.1.0"
  }
}


================================================
FILE: readme.md
================================================
<h1 align="center">
  <br>
  <a href="https://github.com/HR/Crypter"><img src="./build/icons/256x256.png" alt="Crypter" width="180" style= "margin-bottom: 1rem"></a>
  <br>
  Crypter
  <br>
  <br>
</h1>

<h4 align="center">An innovative, convenient and secure crypto app.</h4>
<p align="center">
    <a href="https://github.com/HR/Crypter/releases/latest">
        <img src="https://img.shields.io/badge/Download-4.0-orange.svg"
            alt="Download latest release" style= "margin-bottom: 0.5rem" height="25px">
    </a>
</p>

<p align="center">
    <a href="http://standardjs.com/">
        <img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg"
             alt="Standard JS Code Style">
    </a>
    <a href="https://travis-ci.org/HR/Crypter">
        <img src="https://travis-ci.org/HR/Crypter.svg?branch=master"
             alt="Travis Build">
    </a>
    <a href="https://ci.appveyor.com/project/HR/crypter">
        <img src="https://ci.appveyor.com/api/projects/status/4pa8cnlb1qnyj1xd/branch/master?svg=true"
             alt="AppVeyor Build">
    </a>
    <a href="https://coveralls.io/github/HR/Crypter?branch=master">
        <img src="https://coveralls.io/repos/github/HR/Crypter/badge.svg?branch=master"
             alt="Test Coverage">
    </a>
    <a href="https://codeclimate.com/github/HR/Crypter?branch=master">
        <img src="https://codeclimate.com/github/HR/Crypter/badges/gpa.svg"
             alt="CodeClimate GPA">
    </a>
    <a href="https://github.com/HR/Crypter/releases/latest">
        <img src="https://img.shields.io/github/downloads/hr/crypter/total"
             alt="GitHub all releases downloads" >
    </a>
</p>
<br>

> Encrypt unlimited bits. Remember only a bit.

**Crypter** is a cross-platform crypto app that makes encryption and decryption
convenient while still upholding strong security. It tackles one of the weakest
links in most security systems today - weak passwords. It simplifies secure
password generation and management and requires you to only remember one bit -
your MasterPass.

[Crypter v4.0](https://github.com/HR/Crypter/releases/tag/v4.0.0) is a crypto
app that can decrypt and encrypt any arbitrary data this includes files and
folders. This version has been released and fully tested for macOS (OSX), Linux
(for all distros via [AppImage](http://appimage.org/)) and Windows (32 & 64
bit). All core modules (modules that provide the core functionality) are fully
tested.

[Crypter v5.0](https://github.com/HR/Crypter/milestone/4) will save your MasterPass
in your OS's Keychain so you won't have to enter it every time you open the app.
To help speed up the development please send a PR for what's left do https://github.com/HR/Crypter/milestone/4

Please open an issue if you have any suggestions and add improvements via PRs!

Also checkout [Ciphora](https://github.com/HR/ciphora) (https://github.com/HR/ciphora)
a decentralized end-to-end encrypted messaging app.

Link to this README: https://git.io/Crypter.info

---

# Contents

<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->

- [Installation](#installation)
- [Screens](#screens)
- [Crypto](#crypto)
  - [Encryption process](#encryption-process)
  - [Decryption process](#decryption-process)
  - [Public credentials](#public-credentials)
- [CRYPTO file](#crypto-file)
  - [Format](#format)
  - [Reusing the same MasterPass](#reusing-the-same-masterpass)
  - [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
- [Security](#security)
  - [Security-first practice](#security-first-practice)
  - [MasterPass](#masterpass)
  - [MasterPassKey](#masterpasskey)
- [FAQs](#faqs)
  - [How do I encrypt a file or folder?](#how-do-i-encrypt-a-file-or-folder)
  - [How do I decrypt a CRYPTO file?](#how-do-i-decrypt-a-crypto-file)
  - [How do I encrypt multiple files?](#how-do-i-encrypt-multiple-files)
  - [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)
  - [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)
  - [Why can't I decrypt a CRYPTO file with the same MasterPass?](#why-cant-i-decrypt-a-crypto-file-with-the-same-masterpass)
  - [Where are my encrypted/decrypted files/folders placed?](#where-are-my-encrypteddecrypted-filesfolders-placed)
  - [How can I access Crypter's preferences?](#how-can-i-access-crypters-preferences)
  - [How can I reset my MasterPass?](#how-can-i-reset-my-masterpass)
  - [What is a valid MasterPass?](#what-is-a-valid-masterpass)
  - [What are MasterPass credentials?](#what-are-masterpass-credentials)
  - [How can I export my MasterPass credentials?](#how-can-i-export-my-masterpass-credentials)
  - [How can I import my MasterPass credentials?](#how-can-i-import-my-masterpass-credentials)
- [Development](#development)
  - [Configurations](#configurations)
  - [Install (dependencies)](#install-dependencies)
  - [Run](#run)
  - [Test](#test)
  - [Build](#build)
- [License](#license)

## <!-- /TOC -->

## Installation

All prebuilt binaries for all major platforms are available under
[releases](https://github.com/HR/Crypter/releases/latest).

Crypter is also on [Homebrew Cask](https://formulae.brew.sh/cask/crypter) for
macOS. So to install it, simply run the following command in the Terminal:

```bash
$ brew install --cask crypter@4.0.0
```

<br/>

## Screens

<p align="center">
  <img src="/.github/Welcome_screen.png?raw=true" alt="Welcome screen" width="100%">
  <img src="/.github/Crypter_main_screen.png?raw=true" alt="Crypter screen" width="40%">
  <img src="/.github/MasterPass_screen.png?raw=true" alt="MasterPass screen" width="40%">
  <img src="/.github/Settings_screen.png?raw=true" alt="Settings screen" width="85%">
</p>

<br/>

## Crypto

> One key to derive them all!

Crypter derives a MasterPassKey from the MasterPass obtained at setup by using
the PBKDF2 key derivation algorithm (see below for the specification). It
then uses PBKDF2 to derive a number of encryption keys from the MasterPassKey
that can be used for the encryption of files. This method allows for the
generation of very secure encryption keys for data encryption. Moreover, by
publicly storing the credentials used to derive the MasterPassKey and the salts
used to derive the encryption keys, you are able to produce the encryption keys
at will and without needing to store them securely. Your MasterPass is the only
thing that you need to remember.

Crypter never directly encrypts anything with your MasterPass. Instead, it
derives a MasterPassKey from it, which it then uses to derive the
encryption key used to encrypt your file. Every time a file is decrypted,
the encryption key is re-derived from the MasterPassKey. Every time you set the
MasterPass through the setup or reset it through Verify MasterPass, the
MasterPassKey is derived from the MasterPass using a newly generated set of
(random) credentials. These credentials are used to re-derive the MasterPassKey
every time that Crypter is executed (i.e. the app is launched).

Authentication with the AES-256-GCM symmetric block cipher is used by default.
This ensures that data integrity is verified on decryption and allows the app
to detect tampering or data corruption.

The following are the crypto defaults and can be found under `app/config.js`:

```js
// Crypto defaults
{
  ITERATIONS: 50000, // file encryption key derivation iterations
  KEYLENGTH: 32, // encryption key length
  IVLENGTH: 12, // initialisation vector length
  ALGORITHM: 'aes-256-gcm', // encryption algorithm
  DIGEST: 'sha256', // digest function
  HASH_ALG: 'sha256', // hashing function
  MPK_ITERATIONS: 100000 // MasterPassKey derivation iterations
}
```

### Encryption process

When encrypting a file, Crypter first creates a temporary hidden directory,
namely '.crypting'. It then encrypts the user-selected file with the crypto
defaults and flushes the encrypted data to a file in the directory, namely
'data'. If it is a directory then it is compressed first (tar). It also writes
the public credentials to a file within the same directory, namely 'creds'.
Finally, Crypter compresses the directory to a tar archive with the name of the
user-selected file and the '.crypto' extension appended to it.

### Decryption process

The decryption process is essentially the inverse of the encryption process.
During decryption, Crypter creates a temporary hidden directory named
'.decrypting'. It then reads the credentials from the creds file and decrypts
the data file into the original file or directory (after decompressing it) with
its original name and extension, as deduced from the CRYPTO file name (e.g. the
extension for "file.txt.crypto" would be ".txt").

### Public credentials

Certain credentials are required to decrypt the encrypted data. These are
needed to reconstruct the particular encryption key and to verify data integrity.
These can be stored publicly without compromising security since it is fairly
impossible (by current standards) to reconstruct the encryption key without the
MasterPass and its credentials. These credentials are stored in the creds file of
the [CRYPTO file](#crypto-file) archive (as delineated above) in the following
format:

#### v1

```
Crypter#iv#authTag#salt#dir
```

#### v2

Uses JSON

```json
{
  "type": "CRYPTO",
  "iv": "...",
  "authTag": "...",
  "salt": "...",
  "isDir": true || false
}
```

The `dir` part is only included for directories
<br/>

## CRYPTO file

### Format

A CRYPTO file is the product of the Crypter encryption process. This file
stores both the encrypted version of the user file and the public credentials
needed to encrypt and decrypt it. It has a `.crypto` file extension, which
is appended to the full file name (including the extension) of the file
originally encrypted. The file itself is a tar archive in the following
structure:

```c
someFile.crypto
├── data // the encrypted version of the user selected file
└── creds // the public credentials used to encrypt it
```

### Reusing the same MasterPass

If you attempt to decrypt a CRYPTO file by _resetting to a specific
MasterPass_ or _setting an identical MasterPass on a different machine_,
you will likely encounter the following error:

```
ERROR: Unsupported state or unable to authenticate data
```

This issue occurs because the MasterPassKey that was originally used to
derive the encryption key on is **not the same** as the MasterPassKey
derived with the reused MasterPass. Since Crypter uses randomness to
generate secure credentials, this second set of credentials will be quite
**different** from the original set. As a result, the derived encryption key is
incorrect and yields this error.

See [Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)
for instructions on how to successfully reuse the same MasterPass.

### Achieving portability and same MasterPass reuse

To achieve portability on Crypter, the set of MasterPassKey credentials
need to be exported from Crypter on the source machine<sup>[1](#source)</sup>
and imported into Crypter on the target machine<sup>[2](#target)</sup>.

This can be achieved in two simple steps:

<ol>
  <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>
  <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>
</ol>

Please refer to the FAQs for instructions on how to perform the above steps.

<hr>

<a name="source"></a> [1] The machine where the CRYPTO file was initially
encrypted.

<a name="target"></a> [2] The machine where you wish to decrypt the CRYPTO
file.

<br/>

## Security

### Security-first practice

Crypter follows a security-first practice. This means that security is its
highest priority and first consideration. Thus, while Crypter seeks
to make encryption more convenient, it always defers to maintaining
a high level of security.

### MasterPass

Crypter never stores your MasterPass in memory or on the filesystem. This
substantially improves the security of your MasterPass. You are only asked to
enter the MasterPass when you first set, reset or verify it. Whenever you enter
your MasterPass, Crypter derives a MasterPassKey (using a set of generated
credentials) and then immediately discards the MasterPass. The MasterPassKey is
then securely stored in memory and used to derive the encryption keys. Since
these credentials are derived via a one-way function, they cannot be used in
any way to derive the MasterPass.

### MasterPassKey

Crypter uses a WeakMap to store the MasterPassKey inside the MasterPassKey class
using a closure function. This makes the MasterPassKey data held in the object
(externally) inaccessible, consequently increasing the protection of the
MasterPassKey. The MasterPassKey is never flushed to the filesystem and is always
stored in (main) memory. Since JS does not give control over or allow such a
low-level operation as wiping memory, the program relies on the garbage
collection and volatility of the main memory for the permanent erasure of the
MasterPassKey stored in memory.

A decent number of iterations (see the above specifications) are used in the
derivation of the MasterPassKey to mitigate brute-force attacks. A good
amount of iterations are also used during the derivation of the encryption
keys from the MasterPassKey. Consequently, performance and speed are not
significantly compromised. For critical applications, you may choose to
select 10,000,000 iterations instead of the default number
(in app/core/crypto.js). Please refer to
http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf for
more information.

Crypter generates a new set of random credentials for deriving the MasterPassKey
every time the MasterPass is set (at setup) or reset. Crypter employs randomness
to mitigate brute-force attacks and thus drastically improve security.

<br/>

## FAQs

### How do I encrypt a file or folder?

If you haven't already, walk through the setup and set your MasterPass.
To encrypt a file or folder, first launch Crypter and verify your MasterPass.
After doing so successfully, you will see the main Crypter window with an orange
area. Here, you can simply drag-and-drop or click to select the file/folder you
wish to encrypt. Once Crypter is done encrypting your file/folder, it will show
you the encryption information (i.e. the encryption key and the path of the
encrypted file) in a new window. To encrypt another file/folder simply click the
back arrow at the bottom left and start-over ;)

### How do I decrypt a CRYPTO file?

> The following instructions assume that the CRYPTO file that you wish to
> decrypt is being used with the same MasterPass that you set at setup and also
> that you have not reset it since that time. If this is not the case, please refer
> to [Reusing the same MasterPass](#reusing-the-same-masterpass).

To decrypt a CRYPTO file, first launch Crypter and verify your MasterPass. After
doing so successfully, you will see the main Crypter window with an orange area.
Here, you can simply drag-and-drop or click to select the CRYPTO file that you
wish to decrypt. After a few seconds, the process will complete and you will see
some information about the file and its original encryption in a new window. By
default, the decrypted file has the same name as the name of the original file
(i.e. the encrypted file name without the `.crypto` at the end).

### How do I encrypt multiple files?

Crypter can encrypt an entire folder so you can put them in a folder or,
alternatively, compress them into an archive (like a `.zip`) and then just pass
it to Crypter ;)

### Why am I getting the "Corrupted Crypter file or trying to decrypt on a different machine." error?

This error means that either your Crypter file (i.e. the `data` file) is
corrupt/tempered, that you are on a different machine than the one originally
used to encrypt the file or that you have previously reset your MasterPass.
For the last two cases, please refer to
[Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse).

### Why can't I decrypt a CRYPTO file on a different machine with the same MasterPass?

Please refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)

### Why can't I decrypt a CRYPTO file with the same MasterPass?

Please refer to [Reusing the same MasterPass](#reusing-the-same-masterpass) and
[Achieving portability and same MasterPass reuse](#achieving-portability-and-same-masterpass-reuse)

### Where are my encrypted/decrypted files/folders placed?

By default, every source file that you encrypt/decrypt gets encrypted/decrypted
to the same directory where the source file is located.

### How can I access Crypter's preferences?

You can access Crypter's preferences by either clicking on the cog icon in the
main Crypter window or by going to `Crypter > Preferences...` from the menu.

### How can I reset my MasterPass?

You can reset your MasterPass by clicking on the "Forgot it" link in the Verify
MasterPass window. This takes you to a new screen where you can enter a new, valid
MasterPass. Once you've entered it, click the 'Reset' button and you'll be sent
back to the verify screen where you can verify your new MasterPass.

### What is a valid MasterPass?

Crypter will not allow you to set an invalid MasterPass. A MasterPass is valid
when it adheres to the following rules:

- It is at least 8 characters long
- It has at least one uppercase alphabetic character (A-Z)
- It has at least one lowercase alphabetic character (a-z)
- It has at least one numeric character (0-9)
- It has at least one special character (\$@!%\*#?&)

These rules are enforced via the following regular expression:

```javascript
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@!%*#?&]).{8,}$/;
```

### What are MasterPass credentials?

MasterPass credentials are a set of values that are required to derive the
MasterPassKey from the MasterPass. These values have a pseudo-random element and
are cryptographically linked. Every MasterPass that is set or reset with Crypter
has a unique set of MasterPass credentials. These yield a distinct MasterPassKey,
even when a MasterPass is reused.

### How can I export my MasterPass credentials?

To export your MasterPass credentials, you can first open Crypter's
preferences (see above). From this screen, click on the "Export" button. A
dialog will appear from which you can select the folder where you wish to export
the credentials. A success message will confirm a successful export. The
exported MasterPass credentials file is always named `credentials.crypter`.

### How can I import my MasterPass credentials?

To import a set of MasterPass credentials, you can first open Crypter's
preferences (see above). From this screen, click on the "Import" button. A
dialog will appear from which you can locate your `credentials.crypter` file.
After you select it, a success message will confirm a successful import and
you will have to verify the MasterPass for the credentials.

NOTE: while Crypter does not require the MasterPass credentials file to be
exactly named `credentials.crypter`, it does require the file's contents to
be unaltered from when it was exported from Crypter. If it has been altered,
the import may fail.

<br/>

## Development

Crypter is developed in the "dev" branch, which may be unstable at times.
This branch should typically be used for pull requests.

The "master" branch will always be kept stable.

### Configurations

All major configurations that you can apply are found under `app/config.js`.
This includes changes to certain cryptography settings. Please be advised
that altering these may break functionality and
[portability](#achieving-portability-and-same-masterpass-reuse).

### Install (dependencies)

To install all dependencies, run:

```
$ yarn install
```

### Run

Since Crypter uses gulp, please install it globally if you have not already
done so. To start Crypter, run:

```
$ gulp
```

### Test

Crypter primarily uses mocha and chai for testing. Since the project uses a
lot of JS ES6 syntax, babel is also used as a transpiler. To run all the tests,
execute:

```
$ yarn test
```

Crypter uses istanbul for coverage. To run test coverage, execute:

```
$ yarn run coverage
```

### Build

Crypter's binaries (available under releases) have been built using
Electron. Since Crypter uses electron-builder to build binaries,
you must install it globally:

```
$ npm install electron-builder@next -g
```

To build the app for **macOS**, run:

```
$ yarn run build:mac
```

To build the app for **Linux**, run:

```
$ sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils
$ yarn run build:lin
```

To build the app for **Windows** x84 and/or x64, run:

```
$ yarn run build:win
```

## License

The MIT License (MIT)

Copyright (c) Habib Rehman (https://git.io/HR)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished todo so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: script/appveyor-build.cmd
================================================
@setlocal enableextensions enabledelayedexpansion
@echo off
get-host
set keyword=build

If NOT %APPVEYOR_REPO_COMMIT_MESSAGE:build=%==x%APPVEYOR_REPO_COMMIT_MESSAGE% (
  REM Commit message contains '[build]' so build
  echo Building Crypter
  build.cmd
) else (
  REM Commit message does not contain '[build]' so just test and skip build
  echo Testing Crypter
  test.cmd
)
endlocal

================================================
FILE: script/build.cmd
================================================
echo Building Crypter
REM cd %APPVEYOR_BUILD_FOLDER%
set NODE_ENV=production
npm install electron-builder@next -g
npm install --production
npm run build:win

================================================
FILE: script/build_ml.sh
================================================
#!/bin/bash

echo "Building for: $TRAVIS_OS_NAME"
echo "CWD: $PWD"
echo "Node $(node --version)"
echo "NPM $(npm --version)"

# make for production
export NODE_ENV=production
npm install electron-builder -g
npm prune

if [ "$TRAVIS_OS_NAME" == "linux" ]; then
  # to build for linux
  echo "Building for linux"
  docker run --rm \
        --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') \
        -v ${PWD}:/project \
        -v ~/.cache/electron:/root/.cache/electron \
        -v ~/.cache/electron-builder:/root/.cache/electron-builder \
        electronuserland/builder:wine \
        /bin/bash -c "npm i -g electron-builder && yarn run build:lin"
else
  echo "Building for mac"
  yarn run build:mac
fi
# zip -r dist/**/*.zip ./github/RELEASE


================================================
FILE: script/darwin-build.sh
================================================
echo "pwd: "$PWD
export NODE_ENV=production

# remove any existing distribution
rm -rf dest

npm install --production

electron-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
cp ./github/RELEASE ./dest/Crypter-darwin-x64/RELEASE
cp ./license ./dest/Crypter-darwin-x64
zip -9r ./dest/Crypter-darwin-x64 ./dest/Crypter-darwin-x64


================================================
FILE: script/resolveNodeV.js
================================================
const {app} = require('electron')
const fs = require('fs-extra')
const FILE_PATH = '.nvmrc'
console.log("Electron node version is: "+process.versions.node)
fs.writeFile(FILE_PATH, process.versions.node, function (err) {
  if (err) return console.log('Error writing file: ' + err)
  app.quit()
})


================================================
FILE: script/test.cmd
================================================
node --version
npm --version
REM install all deps
npm install --no-optional
REM run tests
npm test

================================================
FILE: script/travis-build.sh
================================================
#!/bin/bash

# set -ev
# Just exit when fail dont print code to be exec
set +e

export TEST_RUN=true
echo "CC: $CC"
echo "CXX: $CXX"
echo "OS Name: $TRAVIS_OS_NAME"
echo "Node $(node --version)"
echo "NPM $(npm --version)"

# Install deps
yarn install --ignore-optional
yarn prune

# Test and get coverage
yarn run coverage
yarn run coveralls
# - npm run codeclimate

# End-to-end OSX testing
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
#   export DISPLAY=:99.0
#   sh -e /etc/init.d/xvfb start
#   sleep 3
#   unset TEST_RUN
#   npm run xtest
# fi

unset TEST_RUN


================================================
FILE: script/win-build.sh
================================================
echo "pwd: "$PWD
export NODE_ENV=production
export DISPLAY=':99.0'

# remove any existing distribution
rm -rf dest

# install deps
sudo apt-get wine
sudo dpkg --add-architecture i386
sudo add-apt-repository ppa:wine/wine-builds
sudo apt-get update
sudo apt-get install --install-recommends winehq-devel
sudo apt-get install -y xvfb

# start Xvfb server
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1

npm install --production

./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
cp ./github/RELEASE ./dest/Crypter-win32-x64/RELEASE
cp ./license ./dest/Crypter-win32-x64
zip -r ./dest/Crypter-win32-x64 ./dest/Crypter-win32-x64


================================================
FILE: test/test.js
================================================
'use strict'
const assert = require('assert')
const path = require('path')
const expect = require('chai')
  .expect
const crypto = require('../app/core/crypto.js')
const Db = require('../app/core/Db')
const MasterPassKey = require('../app/core/MasterPassKey')
const MasterPass = require('../app/core/MasterPass')
const scrypto = require('crypto')
const _ = require('lodash')
const fs = require('fs-extra')
const logger = require('../app/node_modules/electron-log/src')
logger.transports.file.level = false;
logger.transports.console.level = false;

console.log(`cwd: ${process.cwd()}`)
console.log(`__dirname: ${__dirname}`)

let checkFileSync = function (path) {
  try {
    fs.accessSync(path, fs.F_OK)
  } catch (err) {
    if (err.code === 'ENOENT') return false
  }
  return true
}

describe("Crypter Core Modules' tests", function () {
  // Declare globals
  global.creds = {}
  global.paths = {
    mdb: path.join(__dirname, '/tmp/mdb'),
    tmp: path.join(__dirname, '/tmp')
  }
  global.MasterPassKey = new MasterPassKey(scrypto.randomBytes(32))

  // Declare constants
  const KEY_LENGTH = 32 // 32 bytes
  const TEST_FOLDER_PATH = `${global.paths.tmp}/test`
  const TEST_FOLDER_FILE_PATH = `${TEST_FOLDER_PATH}/test.txt`
  const ENCRYTED_TEST_FOLDER_PATH = `${TEST_FOLDER_PATH}.crypto`
  const DECRYTING_FOLDER_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FOLDER_PATH)}/.decrypting`
  const TEST_FILE_PATH = `${global.paths.tmp}/test.txt`
  const ENCRYTED_TEST_FILE_PATH = `${TEST_FILE_PATH}.crypto`
  const DECRYTED_TEST_FILE_PATH = `${global.paths.tmp}/test.txt`
  const TEST_FILE_CONTENTS = '#Crypter'
  const TEST_CONTENTS_ENCODING = 'utf8'
  const DECRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.decrypting`
  const ENCRYTING_TEMP_DIR_PATH = `${path.dirname(ENCRYTED_TEST_FILE_PATH)}/.crypting`
  const DB_TEST_FILE_PATH = `${global.paths.tmp}/db`
  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]
  const BUF2HEX_TEST_HEX_EXPECTED = 'f81b9ec942d850fe5168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'
  const SAFEEQ_TEST_HEX = 'f81b9ec942d850f35168ee0901e7866a08ca2c59e73d638ba7a215d87f558e56'

  // Before all tests have run
  before(() => {
    // create temporary dir
    fs.ensureDirSync(global.paths.tmp)
    process.chdir(global.paths.tmp)
    console.log(`    > Changed cwd to: ${process.cwd()}`)
    global.mdb = new Db(global.paths.mdb, function () {})
  })

  // After all tests have run
  after(() => {
    // remove temporary dir
    fs.removeSync(global.paths.tmp)
  })

  /**
   * Crypto module.js
   ******************************/

  describe('Crypto module', function () {
    it('should convert string buffer to hex string', function () {
      const b2hr = crypto.buf2hex(BUF2HEX_TEST_ARR)
      expect(b2hr)
        .to.equal(BUF2HEX_TEST_HEX_EXPECTED)
    })
    it('should check if two strings are equal time safely', function () {
      expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, BUF2HEX_TEST_HEX_EXPECTED))
        .to.be.true
      expect(crypto.timingSafeEqual(BUF2HEX_TEST_HEX_EXPECTED, SAFEEQ_TEST_HEX))
        .to.be.false
      expect(crypto.timingSafeEqual(SAFEEQ_TEST_HEX, BUF2HEX_TEST_HEX_EXPECTED))
        .to.be.false
    })
    describe('deriveKey (and genPassHash)', function () {
      // deriveKey uses genPassHash Promise internally. Testing the derived
      // MasterPassKey before and after also suffienctly tests genPassHash
      // (indirectly test the hash before and after)

      const masterpass = 'crypto#101'
      it('should deriveKey using a MasterPass correctly when salt is buffer', function () {
        let mpkey
        return crypto.deriveKey(masterpass, null)
          .then((dmp) => {
            // derived salt should be a Buffer
            expect(dmp.salt instanceof Buffer)
              .to.be.true
            mpkey = dmp.key
            return crypto.deriveKey(masterpass, dmp.salt)
          })
          .then((dmp) => {
            // The newly derive MPK should equal to the originally derived MPK
            expect(dmp.key.toString('hex'))
              .to.equal(mpkey.toString('hex'))
          })
      })

      it('should deriveKey using a MasterPass correctly with persistent salt', function () {
        let mpkey
        return crypto.deriveKey(masterpass, null)
          .then((dmp) => {
            // Serialize and deserialize the derived salt
            const pdmpsalt = JSON.parse(JSON.stringify(dmp.salt))
            mpkey = dmp.key
            // Used deserialized salt to derive key
            return crypto.deriveKey(masterpass, pdmpsalt)
          })
          .then((dmp) => {
            // The deserialized salt should successfully create a Buffer
            expect(Buffer.isBuffer(dmp.salt))
              .to.be.true
            // The deserialized salt should have been used to recreate the
            // Buffer orginally used to derive the key correctly which should
            // result in the newly derive MPK equalling to the originally
            // derived MPK
            expect(dmp.key.toString('hex'))
              .to.equal(mpkey.toString('hex'))
          })
      })
    })

    describe('Encryption', function () {
      // Before any tests are run in this suite
      before(function () {
        // create a test file (for encryption)
        fs.writeFileSync(TEST_FILE_PATH, TEST_FILE_CONTENTS, TEST_CONTENTS_ENCODING)
      })
      after(function () {
        fs.removeSync(ENCRYTING_TEMP_DIR_PATH)
      })
      describe('encrypt promise', function () {
        it('should encrypt file with pass without errors & have all expected creds', function () {
          return crypto.encrypt(TEST_FILE_PATH, global.MasterPassKey.get())
            .then((creds) => {
              // The encrypted file should exist at the ENCRYTED_TEST_FILE_PATH
              expect(checkFileSync(ENCRYTED_TEST_FILE_PATH))
                .to.be.true
              expect(checkFileSync(ENCRYTING_TEMP_DIR_PATH))
                .to.be.false
              // Creds should have all the expected properties
              expect(creds)
                .not.be.empty
             
Download .txt
gitextract_a8kqtnjq/

├── .babelrc
├── .codeclimate.yml
├── .github/
│   └── ISSUE_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .travis.yml
├── SECURITY.md
├── app/
│   ├── config.js
│   ├── core/
│   │   ├── Db.js
│   │   ├── MasterPass.js
│   │   ├── MasterPassKey.js
│   │   └── crypto.js
│   ├── index.js
│   ├── package.json
│   ├── src/
│   │   ├── crypter.js
│   │   ├── mainMenu.js
│   │   ├── masterPassPrompt.js
│   │   ├── menu.js
│   │   ├── settings.js
│   │   └── setup.js
│   ├── static/
│   │   ├── crypter.html
│   │   ├── fonts/
│   │   │   └── LICENSE.txt
│   │   ├── js/
│   │   │   ├── common.js
│   │   │   ├── crypter.js
│   │   │   ├── masterpassprompt.js
│   │   │   ├── settings.js
│   │   │   └── setup.js
│   │   ├── masterpassprompt.html
│   │   ├── settings.html
│   │   ├── setup.html
│   │   └── styles/
│   │       ├── crypter.css
│   │       ├── crypter.less
│   │       ├── masterpassprompt.css
│   │       ├── masterpassprompt.less
│   │       ├── mixins.css
│   │       ├── mixins.less
│   │       ├── settings.css
│   │       ├── settings.less
│   │       ├── setup.css
│   │       └── setup.less
│   └── utils/
│       ├── logger.js
│       ├── update.js
│       └── utils.js
├── appveyor.yml
├── build/
│   ├── background.tif
│   ├── crypto.icns
│   ├── icon.icns
│   ├── license.txt
│   └── vicon.icns
├── gulpfile.js
├── license
├── package.json
├── readme.md
├── script/
│   ├── appveyor-build.cmd
│   ├── build.cmd
│   ├── build_ml.sh
│   ├── darwin-build.sh
│   ├── resolveNodeV.js
│   ├── test.cmd
│   ├── travis-build.sh
│   └── win-build.sh
└── test/
    ├── test.js
    └── ui/
        └── test.js
Download .txt
SYMBOL INDEX (40 symbols across 15 files)

FILE: app/config.js
  constant VIEWS_BASE_URI (line 8) | const VIEWS_BASE_URI = `file://${__dirname}/static`

FILE: app/core/Db.js
  function Db (line 18) | function Db (path, cb) {

FILE: app/core/MasterPass.js
  constant SERVICE (line 7) | const crypto = require('./crypto'),
  constant ACCOUNT (line 7) | const crypto = require('./crypto'),

FILE: app/core/MasterPassKey.js
  function MasterPassKey (line 14) | function MasterPassKey (key) {

FILE: app/index.js
  function createWindow (line 264) | function createWindow (window, ...args) {

FILE: app/src/crypter.js
  function encrypt (line 34) | function encrypt(filePath) {
  function decrypt (line 48) | function decrypt(filePath) {
  function cryptFile (line 72) | function cryptFile(file) {

FILE: app/src/mainMenu.js
  method click (line 10) | click() { app.emit('app:check-update') }
  method click (line 12) | click() { app.emit('app:open-settings') }
  method click (line 14) | click() { app.emit('app:quit') }

FILE: app/src/menu.js
  method click (line 41) | click() { shell.openExternal(REPO.DOCS)}
  method click (line 43) | click() { shell.openExternal(REPO.REPORT_ISSUE)}
  method click (line 44) | click() { shell.openExternal(REPO.URL)}
  method click (line 45) | click() { shell.openExternal(REPO.FORK)}

FILE: app/static/js/common.js
  function getUrlParameter (line 19) | function getUrlParameter(name) {
  function navigate (line 26) | function navigate (panel) {
  function validateMasterPass (line 33) | function validateMasterPass(field, errLabel) {

FILE: app/static/js/crypter.js
  function disableFileInput (line 75) | function disableFileInput () {
  function enableFileInput (line 82) | function enableFileInput () {
  function enableUI (line 95) | function enableUI () {
  function disableUI (line 100) | function disableUI () {
  function showFile (line 105) | function showFile (path) {
  function showOpenDialog (line 109) | function showOpenDialog (properties) {
  function handler (line 132) | function handler () {

FILE: app/static/js/masterpassprompt.js
  constant DONE_TIMEOUT (line 7) | const DONE_TIMEOUT = 2000

FILE: app/static/js/setup.js
  constant NAV2DONE_TIMEOUT (line 8) | const NAV2DONE_TIMEOUT = 2000
  constant DONE_TIMEOUT (line 10) | const DONE_TIMEOUT = NAV2DONE_TIMEOUT + 5000
  constant SPEED (line 12) | const SPEED = 3000
  constant OFFSET (line 13) | const OFFSET = SPEED * 1.1

FILE: app/utils/update.js
  constant USER_AGENT (line 4) | const USER_AGENT = 'Crypter/x Wubba Lubba Dub Dub'
  constant VERSION_REGEX (line 5) | const VERSION_REGEX = /[\.v]+/g
  constant VERSION (line 6) | const VERSION = parseV(app.getVersion())
  function parseV (line 8) | function parseV(str) {

FILE: gulpfile.js
  constant LESS_FILES (line 13) | const LESS_FILES = './app/static/styles/*.less'
  constant ELECTRON (line 14) | const ELECTRON = __dirname + '/node_modules/.bin/electron'
  constant DEBUG (line 15) | const DEBUG = false
  function electron (line 19) | async function electron () {

FILE: script/resolveNodeV.js
  constant FILE_PATH (line 3) | const FILE_PATH = '.nvmrc'
Condensed preview — 63 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (223K chars).
[
  {
    "path": ".babelrc",
    "chars": 38,
    "preview": "{\n  \"presets\": [\"@babel/preset-env\"]\n}"
  },
  {
    "path": ".codeclimate.yml",
    "chars": 676,
    "preview": "engines:\n  csslint:\n    enabled: false\n  duplication:\n    enabled: true\n    config:\n      languages:\n      - javascript\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 961,
    "preview": "### Prerequisites\n\n* [ ] Can you reproduce the problem?\n* [ ] Did you read the entire [README.md](https://github.com/HR/"
  },
  {
    "path": ".gitignore",
    "chars": 1247,
    "preview": "### OSX ###\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# dev\ndest/\n.cred\ntest/specs\ntest/render.js\n.env\nspec/\ndeprecated/\nscree"
  },
  {
    "path": ".nvmrc",
    "chars": 7,
    "preview": "12.14.1"
  },
  {
    "path": ".travis.yml",
    "chars": 1310,
    "preview": "branches:\n  only:\n    - master\n\nmatrix:\n  include:\n    - os: linux\n      services: docker\n      sudo: required\n      dis"
  },
  {
    "path": "SECURITY.md",
    "chars": 322,
    "preview": "# Security Policy\n\n## Supported Versions\n\nThe following versions will receive security updates.\n\n| Version | Supported  "
  },
  {
    "path": "app/config.js",
    "chars": 3293,
    "preview": "'use strict'\n/**\n * config.js\n * Provides all essential config constants\n ******************************/\n\n// Fixed cons"
  },
  {
    "path": "app/core/Db.js",
    "chars": 4430,
    "preview": "'use strict'\n/**\n * Db.js\n * Custom JSON DB API implementation\n ******************************/\n\nconst _ = require('loda"
  },
  {
    "path": "app/core/MasterPass.js",
    "chars": 2152,
    "preview": "'use strict'\n/**\n * MasterPass.js\n * MasterPass functionality\n ******************************/\n\nconst crypto = require('"
  },
  {
    "path": "app/core/MasterPassKey.js",
    "chars": 1357,
    "preview": "'use strict'\n/**\n * MasterPass.js\n * Provides a way to securely set and retrieve the MasterPass globally\n * MasterPassKe"
  },
  {
    "path": "app/core/crypto.js",
    "chars": 11415,
    "preview": "'use strict'\n/**\n * crypto.js\n * Provides the crypto functionality required\n ******************************/\n\nconst fs ="
  },
  {
    "path": "app/index.js",
    "chars": 7317,
    "preview": "'use strict'\n/**\n * index.js\n * Entry point for app execution\n ******************************/\n\nconst { app, dialog, Bro"
  },
  {
    "path": "app/package.json",
    "chars": 886,
    "preview": "{\n  \"name\": \"Crypter\",\n  \"productName\": \"Crypter\",\n  \"version\": \"5.0.0\",\n  \"description\": \"An innovative, convenient and"
  },
  {
    "path": "app/src/crypter.js",
    "chars": 2831,
    "preview": "const { app, ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, ERRORS, WINDOW_OPTS } = require('../con"
  },
  {
    "path": "app/src/mainMenu.js",
    "chars": 584,
    "preview": "const {app, shell} = require('electron')\nconst menu = require('./menu')\n\nif (process.platform === 'darwin') {\n  menu.uns"
  },
  {
    "path": "app/src/masterPassPrompt.js",
    "chars": 3708,
    "preview": "const { ipcMain, Menu, BrowserWindow } = require('electron')\nconst { VIEWS, WINDOW_OPTS } = require('../config')\nconst M"
  },
  {
    "path": "app/src/menu.js",
    "chars": 916,
    "preview": "const {app, shell} = require('electron')\nconst {REPO} = require('../config')\n\nmodule.exports = [\n  {\n    label: 'Edit',\n"
  },
  {
    "path": "app/src/settings.js",
    "chars": 2498,
    "preview": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst menuTemplate = require('./menu')\nconst {CRYPTO, VI"
  },
  {
    "path": "app/src/setup.js",
    "chars": 2340,
    "preview": "const {app, ipcMain, Menu, BrowserWindow} = require('electron')\nconst {VIEWS, WINDOW_OPTS} = require('../config')\nconst "
  },
  {
    "path": "app/static/crypter.html",
    "chars": 3574,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"styles/crypter.css\" />\n   "
  },
  {
    "path": "app/static/fonts/LICENSE.txt",
    "chars": 11560,
    "preview": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n               "
  },
  {
    "path": "app/static/js/common.js",
    "chars": 2987,
    "preview": "'use strict'\n/**\n * common.js\n * Contains all the functionality that is common between the views\n **********************"
  },
  {
    "path": "app/static/js/crypter.js",
    "chars": 4140,
    "preview": "'use strict'\n/**\n * crypter.js\n * Contains scripts for crypter.html\n ******************************/\nconst dialog = remo"
  },
  {
    "path": "app/static/js/masterpassprompt.js",
    "chars": 2092,
    "preview": "'use strict'\n/**\n * masterpassprompt.js\n * Contains scripts for masterpassprompt.html\n ******************************/\n\n"
  },
  {
    "path": "app/static/js/settings.js",
    "chars": 2360,
    "preview": "'use strict'\n/**\n * settings.js\n * Contains scripts for settings.html\n ******************************/\n\nconst dialog = r"
  },
  {
    "path": "app/static/js/setup.js",
    "chars": 2066,
    "preview": "'use strict'\n/**\n * setup.js\n * Contains scripts for setup.html\n ******************************/\n\n// Delay (in ms) after"
  },
  {
    "path": "app/static/masterpassprompt.html",
    "chars": 2326,
    "preview": "<!doctype html>\n<html>\n\n  <head>\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"styles/masterpassprompt.css"
  },
  {
    "path": "app/static/settings.html",
    "chars": 4117,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"stylesheet\" href=\"styles/settings.css\" />\n  "
  },
  {
    "path": "app/static/setup.html",
    "chars": 4285,
    "preview": "<!doctype html>\n<html>\n\n  <head>\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"styles/setup.css\" charset=\""
  },
  {
    "path": "app/static/styles/crypter.css",
    "chars": 7667,
    "preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
  },
  {
    "path": "app/static/styles/crypter.less",
    "chars": 3517,
    "preview": "// out: ./crypter.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t ==================================================="
  },
  {
    "path": "app/static/styles/masterpassprompt.css",
    "chars": 6443,
    "preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
  },
  {
    "path": "app/static/styles/masterpassprompt.less",
    "chars": 2320,
    "preview": "// out: ./masterpassprompt.css, compress: true\n/*\n\t MasterPassPrompt styles\n\t =========================================="
  },
  {
    "path": "app/static/styles/mixins.css",
    "chars": 5298,
    "preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
  },
  {
    "path": "app/static/styles/mixins.less",
    "chars": 7100,
    "preview": "// out: ./mixins.css, compress: true\n@import (css) \"../../node_modules/normalize.css/normalize.css\";\n\n/* theme colors */"
  },
  {
    "path": "app/static/styles/settings.css",
    "chars": 7921,
    "preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.fancy,button.fancy{animation:OrangeAnimGrad 3s"
  },
  {
    "path": "app/static/styles/settings.less",
    "chars": 4242,
    "preview": "// out: ./settings.css, compress: true\n/* Settings styles\n\t ============================================================"
  },
  {
    "path": "app/static/styles/setup.css",
    "chars": 8173,
    "preview": "@import \"../../node_modules/normalize.css/normalize.css\";body,p{margin:0}.button,body,button{background-color:#FFF;width"
  },
  {
    "path": "app/static/styles/setup.less",
    "chars": 5752,
    "preview": "// out: ./setup.css, compress: true\n/* Setup styles\n\t =================================================================="
  },
  {
    "path": "app/utils/logger.js",
    "chars": 1203,
    "preview": "'use strict'\n/**\n * logger.js\n * Custom logger for debugging\n ******************************/\nconst { createLogger, form"
  },
  {
    "path": "app/utils/update.js",
    "chars": 1685,
    "preview": "const https = require('https')\nconst { app, dialog, shell } = require('electron')\nconst { REPO } = require('../config')\n"
  },
  {
    "path": "app/utils/utils.js",
    "chars": 453,
    "preview": "const { extname } = require('path')\nconst { CRYPTO } = require('../config')\n\nmodule.exports = {\n  isRenderer: () => {\n  "
  },
  {
    "path": "appveyor.yml",
    "chars": 667,
    "preview": "version: \"{build}\"\n\nplatform:\n  - x64\n\nimage: Visual Studio 2019\n\n\ncache:\n  - node_modules\n  - app\\node_modules\n  - '%AP"
  },
  {
    "path": "build/license.txt",
    "chars": 1110,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, fr"
  },
  {
    "path": "gulpfile.js",
    "chars": 3294,
    "preview": "const gulp = require('gulp')\nconst shell = require('child_process').exec\nconst spawn = require('child_process').spawn\nco"
  },
  {
    "path": "license",
    "chars": 1110,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Habib Rehman <H@Rehman.email> (https://git.io/HR)\n\nPermission is hereby granted, fr"
  },
  {
    "path": "package.json",
    "chars": 4491,
    "preview": "{\n  \"name\": \"Crypter\",\n  \"productName\": \"Crypter\",\n  \"version\": \"5.0.0\",\n  \"description\": \"An innovative, convenient and"
  },
  {
    "path": "readme.md",
    "chars": 22469,
    "preview": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://github.com/HR/Crypter\"><img src=\"./build/icons/256x256.png\" alt=\"Crypter\" "
  },
  {
    "path": "script/appveyor-build.cmd",
    "chars": 382,
    "preview": "@setlocal enableextensions enabledelayedexpansion\n@echo off\nget-host\nset keyword=build\n\nIf NOT %APPVEYOR_REPO_COMMIT_MES"
  },
  {
    "path": "script/build.cmd",
    "chars": 156,
    "preview": "echo Building Crypter\nREM cd %APPVEYOR_BUILD_FOLDER%\nset NODE_ENV=production\nnpm install electron-builder@next -g\nnpm in"
  },
  {
    "path": "script/build_ml.sh",
    "chars": 824,
    "preview": "#!/bin/bash\n\necho \"Building for: $TRAVIS_OS_NAME\"\necho \"CWD: $PWD\"\necho \"Node $(node --version)\"\necho \"NPM $(npm --versi"
  },
  {
    "path": "script/darwin-build.sh",
    "chars": 515,
    "preview": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\n\n# remove any existing distribution\nrm -rf dest\n\nnpm install --production\n\ne"
  },
  {
    "path": "script/resolveNodeV.js",
    "chars": 296,
    "preview": "const {app} = require('electron')\nconst fs = require('fs-extra')\nconst FILE_PATH = '.nvmrc'\nconsole.log(\"Electron node v"
  },
  {
    "path": "script/test.cmd",
    "chars": 98,
    "preview": "node --version\nnpm --version\nREM install all deps\nnpm install --no-optional\nREM run tests\nnpm test"
  },
  {
    "path": "script/travis-build.sh",
    "chars": 565,
    "preview": "#!/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"
  },
  {
    "path": "script/win-build.sh",
    "chars": 812,
    "preview": "echo \"pwd: \"$PWD\nexport NODE_ENV=production\nexport DISPLAY=':99.0'\n\n# remove any existing distribution\nrm -rf dest\n\n# in"
  },
  {
    "path": "test/test.js",
    "chars": 20832,
    "preview": "'use strict'\nconst assert = require('assert')\nconst path = require('path')\nconst expect = require('chai')\n  .expect\ncons"
  },
  {
    "path": "test/ui/test.js",
    "chars": 5754,
    "preview": "'use strict'\nconst Application = require('spectron').Application\nconst assert = require('assert')\nconst chai = require('"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the HR/Crypter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 63 files (208.0 KB), approximately 56.4k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!