scrypt = require "scrypt" errors = require "errors" Promise = require "bluebird" # Some custom error types, since the `scrypt` library doesn't have proper error handling errors.create name: "ScryptError" errors.create {name: "ScryptInputError", parents: errors.ScryptError} errors.create {name: "ScryptPasswordError", parents: errors.ScryptError} errors.create {name: "ScryptInternalError", parents: errors.ScryptError} scryptErrorMap = { "getrlimit or sysctl(hw.usermem) failed": 1 "clock_getres or clock_gettime failed": 2 "error computing derived key": 3 "could not read salt from /dev/urandom": 4 "error in OpenSSL": 5 "malloc failed": 6 "data is not a valid scrypt-encrypted block": 7 "unrecognized scrypt format": 8 "decrypting file would take too much memory": 9 "decrypting file would take too long": 10 "password is incorrect": 11 "error writing output file": 12 "error reading input file": 13 "error unkown": -1 } defaultParameters = Promise.promisify(scrypt.params)(0.1, undefined, undefined) getDefaultParameters = (params) -> # This wrapper function is to ensure that we only calculate the parameters once, but can still skip waiting for that if custom parameters were passed in anyway. if params? return params else return defaultParameters normalizePassword = (password) -> if Buffer.isBuffer(password) return password else return new Buffer(password) scryptHandler = (resolve, reject) -> # Well, `scrypt` now returns real Error objects. Except now they don't have error codes anymore... return (err, result) -> if err? errorObj = switch (scryptErrorMap[err.message] ? -1) when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13, -1 then errors.ScryptInternalError when 7, 8 then errors.ScryptInputError when 11 then errors.ScryptPasswordError reject new errorObj(err.message) else if result == true resolve result else if result == false reject new errors.ScryptPasswordError("The password did not match.") else resolve result.toString("base64") module.exports = hash: (password, options = {}, callback) -> # We will still manually promisify, because the behaviour of `scrypt` is not predictable. It may either synchronously throw an error or return a Promise, depending on available ECMAScript features... Promise.try -> getDefaultParameters(options.params) .then (parameters) -> new Promise (resolve, reject) -> scrypt.kdf normalizePassword(password), parameters, scryptHandler(resolve, reject) .nodeify(callback) verifyHash: (password, hash, callback) -> (new Promise (resolve, reject) -> hashBuffer = new Buffer(hash, "base64") scrypt.verifyKdf hashBuffer, normalizePassword(password), scryptHandler(resolve, reject) ).nodeify(callback) ScryptError: errors.ScryptError InputError: errors.ScryptInputError PasswordError: errors.ScryptPasswordError InternalError: errors.ScryptInternalError scryptLib: scrypt