Skip to content

Commit 14c98f8

Browse files
committed
Prevent throwing errors out of async operations, fixes #6
1 parent acd062c commit 14c98f8

5 files changed

Lines changed: 241 additions & 172 deletions

File tree

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
![bcrypt.js - bcrypt in plain JavaScript](https://raw.github.com/dcodeIO/bcrypt.js/master/bcrypt.png)
22
===========
3-
Optimized bcrypt in plain JavaScript with zero dependencies. Compiled through Closure Compiler using advanced
4-
optimizations, 100% typed code. Compatible to the C++ [bcrypt](https://npmjs.org/package/bcrypt) binding and also
5-
working in the browser.
3+
Optimized bcrypt in plain JavaScript with zero dependencies. Compatible to the C++ [bcrypt](https://npmjs.org/package/bcrypt)
4+
binding and also working in the browser.
65

76
Features ![Build Status](https://travis-ci.org/dcodeIO/bcrypt.js.png?branch=master)
87
--------
@@ -11,7 +10,7 @@ Features ![Build Status](https://travis-ci.org/dcodeIO/bcrypt.js.png?branch=mast
1110
* RequireJS/AMD compatible
1211
* Zero production dependencies
1312
* Small footprint
14-
* Closure Compiler [externs included](https://github.com/dcodeIO/bcrypt.js/blob/master/externs/bcrypt.js)
13+
* Compiled with Closure Compiler using advanced optimizations, [externs included](https://github.com/dcodeIO/bcrypt.js/blob/master/externs/bcrypt.js)
1514

1615
Security considerations
1716
-----------------------

dist/bcrypt.js

Lines changed: 98 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -702,21 +702,30 @@
702702
* @param {Array.<number>} b Bytes to crypt
703703
* @param {Array.<number>} salt Salt bytes to use
704704
* @param {number} rounds Number of rounds
705-
* @param {function(Error, Array.<number>)=} callback Callback receiving the error, if any, and the resulting bytes. If
705+
* @param {function(Error, Array.<number>=)=} callback Callback receiving the error, if any, and the resulting bytes. If
706706
* omitted, the operation will be performed synchronously.
707-
* @returns {Array.<number>} Resulting bytes or if callback has been omitted, otherwise null
707+
* @returns {!Array.<number>|undefined} Resulting bytes if callback has been omitted, otherwise `undefined`
708708
* @private
709709
*/
710710
function _crypt(b, salt, rounds, callback) {
711-
var cdata = C_ORIG.slice();
712-
var clen = cdata.length;
711+
var cdata = C_ORIG.slice(),
712+
clen = cdata.length,
713+
err;
713714

714715
// Validate
715716
if (rounds < 4 || rounds > 31) {
716-
throw(new Error("Illegal number of rounds: "+rounds));
717+
err = new Error("Illegal number of rounds: "+rounds);
718+
if (callback) {
719+
_nextTick(callback.bind(this, err));
720+
return;
721+
} else throw err;
717722
}
718723
if (salt.length != BCRYPT_SALT_LEN) {
719-
throw(new Error("Illegal salt length: "+salt.length+" != "+BCRYPT_SALT_LEN));
724+
err = new Error("Illegal salt length: "+salt.length+" != "+BCRYPT_SALT_LEN);
725+
if (callback) {
726+
_nextTick(callback.bind(this, err));
727+
return;
728+
} else throw err;
720729
}
721730
rounds = 1 << rounds;
722731
var P = P_ORIG.slice();
@@ -728,7 +737,7 @@
728737

729738
/**
730739
* Calcualtes the next round.
731-
* @returns {Array.<number>} Resulting array if callback has been omitted, otherwise null
740+
* @returns {Array.<number>|undefined} Resulting array if callback has been omitted, otherwise `undefined`
732741
* @private
733742
*/
734743
function next() {
@@ -757,27 +766,26 @@
757766
}
758767
if (callback) {
759768
callback(null, ret);
760-
return null;
769+
return;
761770
} else {
762771
return ret;
763772
}
764773
}
765774
if (callback) {
766775
_nextTick(next);
767776
}
768-
return null;
769777
}
770778

771779
// Async
772780
if (typeof callback !== 'undefined') {
773781
next();
774-
return null;
782+
775783
// Sync
776784
} else {
777785
var res;
778786
while (true) {
779-
if ((res = next()) !== null) {
780-
return res;
787+
if ((res = next()) !== undefined) {
788+
return res || [];
781789
}
782790
}
783791
}
@@ -800,32 +808,54 @@
800808
* Internally hashes a string.
801809
* @param {string} s String to hash
802810
* @param {?string} salt Salt to use, actually never null
803-
* @param {function(Error, ?string)=} callback Callback receiving the error, if any, and the resulting hash. If omitted,
811+
* @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash. If omitted,
804812
* hashing is perormed synchronously.
805-
* @returns {?string} Resulting hash if callback has been omitted, else null
813+
* @returns {string|undefined} Resulting hash if callback has been omitted, otherwise `undefined`
806814
* @private
807815
*/
808816
function _hash(s, salt, callback) {
809-
817+
var err;
818+
if (typeof s !== 'string' || typeof salt !== 'string') {
819+
err = new Error("Invalid string / salt: Not a string");
820+
if (callback) {
821+
_nextTick(callback.bind(this, err));
822+
return;
823+
}
824+
else throw err;
825+
}
826+
810827
// Validate the salt
811828
var minor, offset;
812-
if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {
813-
throw(new Error("Invalid salt version: "+salt.substring(0,2)));
829+
if (salt.charAt(0) !== '$' || salt.charAt(1) !== '2') {
830+
err = new Error("Invalid salt version: "+salt.substring(0,2));
831+
if (callback) {
832+
_nextTick(callback.bind(this, err));
833+
return;
834+
}
835+
else throw err;
814836
}
815-
if (salt.charAt(2) == '$') {
837+
if (salt.charAt(2) === '$') {
816838
minor = String.fromCharCode(0);
817839
offset = 3;
818840
} else {
819841
minor = salt.charAt(2);
820842
if (minor != 'a' || salt.charAt(3) != '$') {
821-
throw(new Error("Invalid salt revision: "+salt.substring(2,4)));
843+
err = new Error("Invalid salt revision: "+salt.substring(2,4));
844+
if (callback) {
845+
_nextTick(callback.bind(this, err));
846+
return;
847+
} else throw err;
822848
}
823849
offset = 4;
824850
}
825851

826852
// Extract number of rounds
827853
if (salt.charAt(offset + 2) > '$') {
828-
throw(new Error("Missing salt rounds"));
854+
err = new Error("Missing salt rounds");
855+
if (callback) {
856+
_nextTick(callback.bind(this, err));
857+
return;
858+
} else throw err;
829859
}
830860
var r1 = parseInt(salt.substring(offset, offset + 1), 10) * 10;
831861
var r2 = parseInt(salt.substring(offset + 1, offset + 2), 10);
@@ -838,7 +868,7 @@
838868
saltb = base64_decode(real_salt, BCRYPT_SALT_LEN);
839869

840870
/**
841-
* Finishs hashing.
871+
* Finishes hashing.
842872
* @param {Array.<number>} bytes Byte array
843873
* @returns {string}
844874
* @private
@@ -860,7 +890,7 @@
860890
if (typeof callback == 'undefined') {
861891
return finish(_crypt(passwordb, saltb, rounds));
862892

863-
// Async
893+
// Async
864894
} else {
865895
_crypt(passwordb, saltb, rounds, function(err, bytes) {
866896
if (err) {
@@ -869,7 +899,6 @@
869899
callback(null, finish(bytes));
870900
}
871901
});
872-
return null;
873902
}
874903
}
875904

@@ -893,7 +922,7 @@
893922
} else if (typeof _getRandomValues === 'function') {
894923
_getRandomValues(array);
895924
} else {
896-
throw(new Error("Failed to generate random values: Web Crypto API not available / no polyfill set"));
925+
throw(new Error("Failed to generate random values: Web Crypto API not available / polyfill not set through bcrypt.setRandomPolyfill"));
897926
}
898927
return Array.prototype.slice.call(array);
899928
}
@@ -907,13 +936,13 @@
907936
* @private
908937
*/
909938
function _gensalt(rounds) {
910-
rounds = rounds || 10;
939+
rounds = rounds || GENSALT_DEFAULT_LOG2_ROUNDS;
911940
if (rounds < 4 || rounds > 31) {
912941
throw(new Error("Illegal number of rounds: "+rounds));
913942
}
914943
var salt = [];
915944
salt.push("$2a$");
916-
if (rounds < GENSALT_DEFAULT_LOG2_ROUNDS) salt.push("0");
945+
if (rounds < 10) salt.push("0");
917946
salt.push(rounds.toString());
918947
salt.push('$');
919948
try {
@@ -944,38 +973,41 @@
944973
* @expose
945974
*/
946975
bcrypt.genSaltSync = function(rounds, seed_length) {
947-
if (!rounds) rounds = 10;
976+
if (typeof rounds === 'undefined')
977+
rounds = GENSALT_DEFAULT_LOG2_ROUNDS;
978+
else if (typeof rounds !== 'number')
979+
throw(new Error("Illegal argument types: "+(typeof rounds)+", "+(typeof seed_length)));
948980
return _gensalt(rounds);
949981
};
950982

951983
/**
952984
* Asynchronously generates a salt.
953-
* @param {(number|function(Error, ?string))=} rounds Number of rounds to use, defaults to 10 if omitted
954-
* @param {(number|function(Error, ?string))=} seed_length Not supported.
985+
* @param {(number|function(Error, string=))=} rounds Number of rounds to use, defaults to 10 if omitted
986+
* @param {(number|function(Error, string=))=} seed_length Not supported.
955987
* @param {function(Error, ?string)=} callback Callback receiving the error, if any, and the resulting salt
956988
* @expose
957989
*/
958990
bcrypt.genSalt = function(rounds, seed_length, callback) {
959-
if (typeof seed_length == 'function') {
991+
if (typeof seed_length === 'function') {
960992
callback = seed_length;
961-
seed_length = -1; // Not supported.
993+
seed_length = undefined; // Not supported.
962994
}
963995
var rnd; // Hello closure
964-
if (typeof rounds == 'function') {
996+
if (typeof rounds === 'function') {
965997
callback = rounds;
966998
rnd = GENSALT_DEFAULT_LOG2_ROUNDS;
967-
} else {
968-
rnd = parseInt(rounds, 10);
969999
}
970-
if (typeof callback != 'function') {
971-
throw(new Error("Illegal or missing 'callback': "+callback));
1000+
if (typeof callback !== 'function')
1001+
throw(new Error("Illegal callback: "+callback));
1002+
if (typeof rounds !== 'number') {
1003+
_nextTick(callback.bind(this, new Error("Illegal argument types: "+(typeof rounds))));
1004+
return;
9721005
}
9731006
_nextTick(function() { // Pretty thin, but salting is fast enough
9741007
try {
975-
var res = bcrypt.genSaltSync(rnd);
976-
callback(null, res);
1008+
callback(null, bcrypt.genSaltSync(rnd));
9771009
} catch(err) {
978-
callback(err, null);
1010+
callback(err);
9791011
}
9801012
});
9811013
};
@@ -984,34 +1016,37 @@
9841016
* Synchronously generates a hash for the given string.
9851017
* @param {string} s String to hash
9861018
* @param {(number|string)=} salt Salt length to generate or salt to use, default to 10
987-
* @returns {?string} Resulting hash, actually never null
1019+
* @returns {string} Resulting hash
9881020
* @expose
9891021
*/
9901022
bcrypt.hashSync = function(s, salt) {
991-
if (!salt) salt = GENSALT_DEFAULT_LOG2_ROUNDS;
992-
if (typeof salt === 'number') {
1023+
if (typeof salt === 'undefined')
1024+
salt = GENSALT_DEFAULT_LOG2_ROUNDS;
1025+
if (typeof salt === 'number')
9931026
salt = bcrypt.genSaltSync(salt);
994-
}
1027+
if (typeof s !== 'string' || typeof salt !== 'string')
1028+
throw new Error("Illegal argument types: "+(typeof s)+', '+(typeof salt));
9951029
return _hash(s, salt);
9961030
};
9971031

9981032
/**
9991033
* Asynchronously generates a hash for the given string.
10001034
* @param {string} s String to hash
10011035
* @param {number|string} salt Salt length to generate or salt to use
1002-
* @param {function(Error, ?string)} callback Callback receiving the error, if any, and the resulting hash
1036+
* @param {function(Error, string=)} callback Callback receiving the error, if any, and the resulting hash
10031037
* @expose
10041038
*/
10051039
bcrypt.hash = function(s, salt, callback) {
1006-
if (typeof callback !== 'function') {
1007-
throw(new Error("Illegal 'callback': "+callback));
1008-
}
1009-
if (typeof salt === 'number') {
1040+
if (typeof callback !== 'function')
1041+
throw(new Error("Illegal callback: "+callback));
1042+
if (typeof s === 'string' && typeof salt === 'number') {
10101043
bcrypt.genSalt(salt, function(err, salt) {
10111044
_hash(s, salt, callback);
10121045
});
1013-
} else {
1046+
} else if (typeof s === 'string' && typeof salt === 'string') {
10141047
_hash(s, salt, callback);
1048+
} else {
1049+
_nextTick(callback.bind(this, new Error("Illegal argument types: "+(typeof s)+', '+(typeof salt))));
10151050
}
10161051
};
10171052

@@ -1024,12 +1059,11 @@
10241059
* @expose
10251060
*/
10261061
bcrypt.compareSync = function(s, hash) {
1027-
if(typeof s !== "string" || typeof hash !== "string") {
1062+
if (typeof s !== "string" || typeof hash !== "string")
10281063
throw(new Error("Illegal argument types: "+(typeof s)+', '+(typeof hash)));
1029-
}
10301064
if (hash.length !== 60) return false;
10311065
var comp = bcrypt.hashSync(s, hash.substr(0, hash.length-31));
1032-
var same = comp.length == hash.length;
1066+
var same = comp.length === hash.length;
10331067
var max_length = (comp.length < hash.length) ? comp.length : hash.length;
10341068

10351069
// to prevent timing attacks, should check entire string
@@ -1051,8 +1085,11 @@
10511085
* @expose
10521086
*/
10531087
bcrypt.compare = function(s, hash, callback) {
1054-
if (typeof callback !== 'function') {
1055-
throw(new Error("Illegal 'callback': "+callback));
1088+
if (typeof callback !== 'function')
1089+
throw(new Error("Illegal callback: "+callback));
1090+
if (typeof s !== "string" || typeof hash !== "string") {
1091+
_nextTick(callback.bind(this, new Error("Illegal argument types: "+(typeof s)+', '+(typeof hash))));
1092+
return;
10561093
}
10571094
bcrypt.hash(s, hash.substr(0, 29), function(err, comp) {
10581095
callback(err, hash === comp);
@@ -1067,26 +1104,23 @@
10671104
* @expose
10681105
*/
10691106
bcrypt.getRounds = function(hash) {
1070-
if(typeof hash !== "string") {
1071-
throw(new Error("Illegal type of 'hash': "+(typeof hash)));
1072-
}
1107+
if (typeof hash !== "string")
1108+
throw(new Error("Illegal argument types: "+(typeof hash)));
10731109
return parseInt(hash.split("$")[2], 10);
10741110
};
10751111

10761112
/**
1077-
* Gets the salt portion from a hash.
1113+
* Gets the salt portion from a hash. Does not validate the hash.
10781114
* @param {string} hash Hash to extract the salt from
1079-
* @returns {string} Extracted salt part portion
1115+
* @returns {string} Extracted salt part
10801116
* @throws {Error} If `hash` is not a string or otherwise invalid
10811117
* @expose
10821118
*/
10831119
bcrypt.getSalt = function(hash) {
1084-
if (typeof hash !== 'string') {
1085-
throw(new Error("Illegal type of 'hash': "+(typeof hash)));
1086-
}
1087-
if (hash.length !== 60) {
1120+
if (typeof hash !== 'string')
1121+
throw(new Error("Illegal argument types: "+(typeof hash)));
1122+
if (hash.length !== 60)
10881123
throw(new Error("Illegal hash length: "+hash.length+" != 60"));
1089-
}
10901124
return hash.substring(0, 29);
10911125
};
10921126

0 commit comments

Comments
 (0)