You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

81 lines
2.8 KiB
CoffeeScript

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]
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