-
Notifications
You must be signed in to change notification settings - Fork 20
/
index.js
145 lines (124 loc) · 3.57 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* Module dependencies.
*/
var crypto = require('crypto');
/**
* Bytesize.
*/
var len = 128;
/**
* Iterations. ~300ms
*/
var iterations = 12000;
/**
* Digest.
*/
var digest = 'sha1';
/**
* Set length to `n`.
*
* @param {Number} n
* @api public
*/
exports.length = function(n){
if (0 == arguments.length) return len;
len = n;
};
/**
* Set iterations to `n`.
*
* @param {Number} n
* @api public
*/
exports.iterations = function(n){
if (0 == arguments.length) return iterations;
iterations = n;
};
/**
* Set digest to hash algorithm `hash`.
*
* @param {String} hash
* @api public
*/
exports.digest = function(hash) {
if (0 === arguments.length) return digest;
if( -1 === crypto.getHashes().indexOf(hash)) throw new Error('invalid hash algorithm');
digest = hash;
};
/**
* Hashes a password with optional `salt`, otherwise generate a salt for `pass`
* Will invoke `fn(err, salt, hash)` if `fn` is provided,
* else will return a Promise resolving to object with signature `{ salt: String, hash: String }`
*
* @param {String} password to hash
* @param {String} optional salt
* @param {Function} optional callback, if not provided returns a Promise
* @returns {Promise|undefined} If no callback provided will return a Promise resolving to signature { salt: String, hash: String }
* @api public
*/
exports.hash = function(pwd, salt, fn){
// decide what to do based on argument length
switch(arguments.length) {
case 3: // all three args passed, this is obviously a callback run
if (!pwd) return fn(new Error('password missing'));
if (!salt) return fn(new Error('salt missing'));
generateHash(pwd, salt).then(function(result) { fn(null, result.hash) }).catch(fn)
break;
case 2: // two args passed. decide what to do based on the type passed for salt
if (isString(salt)) { // salt was a string and no callback passed, so caller expects a promise
return generateHash(pwd, salt)
} else { // salt is not a string, so assume it's a function.
fn = salt;
if (!pwd) return fn(new Error('password missing'));
generateHash(pwd).then(function(result) { fn(null, result.salt, result.hash) }).catch(fn)
break;
}
case 1: // just the password was passed, so caller expects a promise
return generateHash(pwd)
default: // nothing was passed, complain
throw new Error('No password provided')
}
return function(done){
fn = done;
}
};
function isString(s) {
return typeof s === 'string'
}
/**
* generate a hash from the given password and salt
* if salt is not passed it will be generated
*
* @param {String} password to hash
* @param {String} optional salt
* @returns {Promise} Promise resolving to object with signature { salt: String, hash: String }
* @api private
*/
function generateHash(pwd, salt) {
return Promise.resolve()
.then(function() { return salt ? salt : generateSalt() })
.then(function(salt) {
return new Promise(function(resolve, reject) {
// iterations, len, and digest are file scoped
crypto.pbkdf2(pwd, salt, iterations, len, digest, function(err, hash){
if (err) { return reject(err); }
resolve({ salt, hash: hash.toString('base64') })
});
})
})
}
/**
* generate a random salt
*
* @returns {Promise} Promise resolving to String value.
* @api private
*/
function generateSalt() {
return new Promise(function(resolve, reject) {
// len is file scoped
crypto.randomBytes(len, function(err, salt) {
if (err) { return reject(err); }
resolve(salt.toString('base64'))
})
})
}