Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export function setRandomFallback(random) {
* @param {number=} rounds Number of rounds to use, defaults to 10 if omitted
* @param {number=} seed_length Not supported.
* @returns {string} Resulting salt
* @throws {Error} If rounds is not an integer
* @throws {Error} If a random fallback is required but not set
*/
export function genSaltSync(rounds, seed_length) {
Expand All @@ -90,6 +91,8 @@ export function genSaltSync(rounds, seed_length) {
throw Error(
"Illegal arguments: " + typeof rounds + ", " + typeof seed_length,
);
if (!Number.isInteger(rounds))
throw Error("Illegal arguments: rounds must be an integer");
if (rounds < 4) rounds = 4;
else if (rounds > 31) rounds = 31;
var salt = [];
Expand All @@ -108,6 +111,7 @@ export function genSaltSync(rounds, seed_length) {
* @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting salt
* @returns {!Promise} If `callback` has been omitted
* @throws {Error} If `callback` is present but not a function
* @rejects {Error} If rounds is not an integer
*/
export function genSalt(rounds, seed_length, callback) {
if (typeof seed_length === "function")
Expand Down Expand Up @@ -172,6 +176,10 @@ export function hash(password, salt, callback, progressCallback) {
function _async(callback) {
if (typeof password === "string" && typeof salt === "number")
genSalt(salt, function (err, salt) {
if (err) {
callback(err);
return;
}
_hash(password, salt, callback, progressCallback);
});
else if (typeof password === "string" && typeof salt === "string")
Expand Down
98 changes: 90 additions & 8 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,111 @@ const tests = [
assert(salt);
assert(typeof salt === "string");
assert(salt.length > 0);

// Negative test cases: non-integer rounds must throw
assert.throws(function () {
bcrypt.genSaltSync(10.5);
}, /Illegal arguments: rounds must be an integer/);
assert.throws(function () {
bcrypt.genSaltSync(Infinity);
}, /Illegal arguments: rounds must be an integer/);

// Positive test cases: valid integers and boundaries (4 to 31)
// Bcrypt salts follow the Modular Crypt Format (MCF): "$<algorithm-id>$<rounds>$<salt>"
// Splitting by "$" yields ["", "<algorithm-id>", "<rounds>", "<salt>"], so parts[2] is always the rounds.
[
{ rounds: 4, expectedCost: "04" },
{ rounds: 10, expectedCost: "10" },
{ rounds: 10.0, expectedCost: "10" }, // 10.0 is represented identically to 10 in JS
{ rounds: 31, expectedCost: "31" },
].forEach(function (testCase) {
var s = bcrypt.genSaltSync(testCase.rounds);
var parts = s.split("$");
assert.strictEqual(parts[2], testCase.expectedCost);
});
done();
},
function genSalt(done) {
bcrypt.genSalt(10, function (err, salt) {
assert(!err);
assert(salt);
assert(typeof salt === "string");
assert(salt.length > 0);
done();
});
// Non-integer rounds without a callback: returns a rejected promise
Promise.all([
assert.rejects(
bcrypt.genSalt(10.5),
/Illegal arguments: rounds must be an integer/,
),
assert.rejects(
bcrypt.genSalt(Infinity),
/Illegal arguments: rounds must be an integer/,
),
])
.then(function () {
// Non-integer rounds with a callback: error is passed to callback
bcrypt.genSalt(10.5, function (err) {
assert(err);
assert(
/Illegal arguments: rounds must be an integer/.test(err.message),
);
bcrypt.genSalt(10, function (err, salt) {
assert(!err);
assert(salt);
assert(typeof salt === "string");
assert(salt.length > 0);
done();
});
});
})
.catch(function (err) {
assert.fail("genSalt rejection test failed: " + err.message);
});
},
function hashSync(done) {
assert.doesNotThrow(function () {
bcrypt.hashSync("hello", 10);
});
assert.notEqual(bcrypt.hashSync("hello", 10), bcrypt.hashSync("hello", 10));
assert.throws(function () {
bcrypt.hashSync("x", 10.5);
}, /Illegal arguments: rounds must be an integer/);
assert.throws(function () {
bcrypt.hashSync("x", Infinity);
}, /Illegal arguments: rounds must be an integer/);
done();
},
function hash(done) {
bcrypt.hash("hello", 10, function (err, hash) {
assert(!err);
assert(hash);
done();
Promise.all([
assert.rejects(
bcrypt.hash("x", 10.5),
/Illegal arguments: rounds must be an integer/,
),
assert.rejects(
bcrypt.hash("x", Infinity),
/Illegal arguments: rounds must be an integer/,
),
])
.then(function () {
bcrypt.hash("x", 10.5, function (err2, hash2) {
assert(err2);
assert(!hash2);
assert(
/Illegal arguments: rounds must be an integer/.test(err2.message),
);
bcrypt.hash("x", Infinity, function (err3, hash3) {
assert(err3);
assert(!hash3);
assert(
/Illegal arguments: rounds must be an integer/.test(
err3.message,
),
);
done();
});
});
})
.catch(function (err) {
assert.fail("hash rejection test failed: " + err.message);
});
});
},
function compareSync(done) {
Expand Down
2 changes: 2 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export declare function setRandomFallback(random: RandomFallback): void;
* Synchronously generates a salt.
* @param rounds Number of rounds to use, defaults to 10 if omitted
* @return Resulting salt
* @throws If rounds is not an integer
* @throws If a random fallback is required but not set
*/
export declare function genSaltSync(rounds?: number): string;
Expand All @@ -27,6 +28,7 @@ export declare function genSaltSync(rounds?: number): string;
* Asynchronously generates a salt.
* @param rounds Number of rounds to use, defaults to 10 if omitted
* @return Promise with resulting salt, if callback has been omitted
* @rejects If rounds is not an integer
*/
export declare function genSalt(rounds?: number): Promise<string>;

Expand Down