diff --git a/README.md b/README.md index 367e9de..b5ab92e 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ module.exports = (sequelize, DataTypes) => { sequelize, tableName: 'Users', modelName: 'User', - underscored: true, }, ) return User diff --git a/index.js b/index.js index 8a2d2bf..8990c23 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,17 @@ /** * Helpers to make using node's built-in scrypt implementation more * straightforward to use and testable - * + * * See https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback * https://stackoverflow.com/questions/62908969/password-hashing-in-nodejs-using-built-in-crypto - * + * */ -const { scrypt, scryptSync, timingSafeEqual, randomBytes } = require('node:crypto') +const { + scrypt, + scryptSync, + timingSafeEqual, + randomBytes, +} = require('node:crypto') /// Node recommends at least 16 at time of writing. 24 is more than that and // also will result in a base64 string which doesn't contain padding, which is @@ -24,18 +29,20 @@ const DEFAULT_KEY_LENGTH = 64 * @extends {Error} * @classdesc An error thrown when there was a problem encountered while parsing a serialized hash * @property {?Exception} cause An exception which was thrown while parsing this string. - * @property {any} malformedString + * @property {any} malformedString */ class ParseError extends Error { - /** - * @param {any} malformedString - The string which could not be parsed - * @param {?Exception} cause - An error which was raised while trying to parse the string - */ - constructor(malformedString, cause) { - super(`error parsing ${malformedString}, expected hash encoded by @techworkers/scrypt-wrapper`) - this.cause = cause - this.malformedString = malformedString - } + /** + * @param {any} malformedString - The string which could not be parsed + * @param {?Exception} cause - An error which was raised while trying to parse the string + */ + constructor(malformedString, cause) { + super( + `error parsing ${malformedString}, expected hash encoded by @techworkers/scrypt-wrapper`, + ) + this.cause = cause + this.malformedString = malformedString + } } /** @@ -45,52 +52,54 @@ class ParseError extends Error { * @property {HashOptions} options The options passed to hash() or hashSync() */ class InvalidOptions extends Error { - /** - * - * @param {HashOptions} options The options which were received that are invalid - */ - constructor(options) { - super(`invalid options were received:\n\tcost: ${options.cost}\n\tkeyLength: ${options.keyLength}\n\tsaltLength: ${options.saltLength}`) - this.options = options - } + /** + * + * @param {HashOptions} options The options which were received that are invalid + */ + constructor(options) { + super( + `invalid options were received:\n\tcost: ${options.cost}\n\tkeyLength: ${options.keyLength}\n\tsaltLength: ${options.saltLength}`, + ) + this.options = options + } - /** - * Check that the given options are actually what they're expected to be - * @param {HashOptions} options - * @param {?function} reject A function called with the error rather than it being thrown - */ - static validateOptions(options, reject) { - const {cost, keyLength, saltLength} = options - // Cost validation - if ( - !(typeof cost === 'number' || cost instanceof Number) - || cost < 13 - || !Number.isInteger(cost) - ) { - const err = new InvalidOptions(options) - if(reject) reject(err) - else throw err - } - // keyLength validation - if ( - !(typeof keyLength === 'number' || keyLength instanceof Number) - || !Number.isInteger(keyLength) - ) { - const err = new InvalidOptions(options) - if(reject) reject(err) - else throw err - } - // saltLength validation - if ( - !(typeof saltLength === 'number' || saltLength instanceof Number) - || saltLength < 16 /* (recommended minimum) */ - || !Number.isInteger(saltLength) - ) { - const err = new InvalidOptions(options) - if(reject) reject(err) - else throw err - } + /** + * Check that the given options are actually what they're expected to be + * @param {HashOptions} options + * @param {?function} reject A function called with the error rather than it being thrown + */ + static validateOptions(options, reject) { + const { cost, keyLength, saltLength } = options + // Cost validation + if ( + !(typeof cost === 'number' || cost instanceof Number) || + cost < 13 || + !Number.isInteger(cost) + ) { + const err = new InvalidOptions(options) + if (reject) reject(err) + else throw err } + // keyLength validation + if ( + !(typeof keyLength === 'number' || keyLength instanceof Number) || + !Number.isInteger(keyLength) + ) { + const err = new InvalidOptions(options) + if (reject) reject(err) + else throw err + } + // saltLength validation + if ( + !(typeof saltLength === 'number' || saltLength instanceof Number) || + saltLength < 16 /* (recommended minimum) */ || + !Number.isInteger(saltLength) + ) { + const err = new InvalidOptions(options) + if (reject) reject(err) + else throw err + } + } } /** @@ -108,54 +117,65 @@ class InvalidOptions extends Error { * - the URL-safe base64-encoded salt * - a dot ('.') * - the URL-safe base64-encoded derived hash - * + * * @param {string} password the plain-text password * @param {?HashOptions} options optional settings * @throws {InvalidOptions} if the options aren't within the expected constraints * @returns {Promise} */ function hash(password, options) { - const cost = options?.cost ?? DEFAULT_COST - const keyLength = options?.keyLength ?? DEFAULT_KEY_LENGTH - const saltLength = options?.saltLength ?? DEFAULT_SALT_LENGTH - const salt = randomBytes(saltLength) - const $cost = 1 << cost - return new Promise((resolve, reject) => { - InvalidOptions.validateOptions({cost, keyLength, saltLength}, reject) - let $hash = cost.toString(16) + '.' - $hash += salt.toString('base64url') + '.' - scrypt(password.normalize(), salt, keyLength, { cost: $cost }, (err, key) => { - if (err) reject(err) - else resolve($hash + key.toString('base64url')) - }) - }) + const cost = options?.cost ?? DEFAULT_COST + const keyLength = options?.keyLength ?? DEFAULT_KEY_LENGTH + const saltLength = options?.saltLength ?? DEFAULT_SALT_LENGTH + const salt = randomBytes(saltLength) + const $cost = 1 << cost + return new Promise((resolve, reject) => { + InvalidOptions.validateOptions({ cost, keyLength, saltLength }, reject) + let $hash = cost.toString(16) + '.' + $hash += salt.toString('base64url') + '.' + scrypt( + password.normalize(), + salt, + keyLength, + { cost: $cost }, + (err, key) => { + if (err) reject(err) + else resolve($hash + key.toString('base64url')) + }, + ) + }) } /** * Like the above hash() function but synchronous. - * + * * @param {string} password the plain-text password * @param {?HashOptions} options optional settings * @throws {InvalidOptions} if the options aren't within the expected constraints * @returns {Promise} */ function hashSync(password, options) { - const cost = options?.cost ?? DEFAULT_COST - const keyLength = options?.keyLength ?? DEFAULT_KEY_LENGTH - const saltLength = options?.saltLength ?? DEFAULT_SALT_LENGTH - InvalidOptions.validateOptions({cost, keyLength, saltLength}) - const salt = randomBytes(saltLength) - const $cost = 1 << cost - let $hash = cost.toString(16) + '.' - $hash += salt.toString('base64url') + '.' - return $hash + scryptSync(password.normalize(), salt, keyLength, { cost: $cost }).toString('base64url') + const cost = options?.cost ?? DEFAULT_COST + const keyLength = options?.keyLength ?? DEFAULT_KEY_LENGTH + const saltLength = options?.saltLength ?? DEFAULT_SALT_LENGTH + InvalidOptions.validateOptions({ cost, keyLength, saltLength }) + const salt = randomBytes(saltLength) + const $cost = 1 << cost + let $hash = cost.toString(16) + '.' + $hash += salt.toString('base64url') + '.' + return ( + $hash + + scryptSync(password.normalize(), salt, keyLength, { cost: $cost }).toString( + 'base64url', + ) + ) } /** * Verify that the given plaintext password derives the given hash. The hash is * expected to be in the format as encoded by the above hash() function, which * also encodes the cost and salt used to create it. - * + * * @param {string} derivedHash The hash as derived and encoded by the above * hash() function * @param {string} password The plaintext password to compare @@ -163,34 +183,34 @@ function hashSync(password, options) { * @returns {Promise} whether the password matches */ function verify(derivedHash, password) { - return new Promise((resolve, reject) => { - var $cost, $salt, $key - try { - [$cost, $salt, $key] = derivedHash.split('.') - } catch (e) { - return reject(new ParseError(derivedHash, e)) - } - if (!$cost || !$salt || !$key) { - return reject(new ParseError(derivedHash)) - } - var cost, salt, key - try { - cost = 1 << Number.parseInt($cost, 16) - salt = Buffer.from($salt, 'base64url') - key = Buffer.from($key, 'base64url') - } catch (e) { - return reject(new ParseError(derivedHash, e)) - } - scrypt(password.normalize(), salt, key.length, { cost }, (err, newKey) => { - if (err) reject(err) - else resolve(timingSafeEqual(key, newKey)) - }) + return new Promise((resolve, reject) => { + var $cost, $salt, $key + try { + ;[$cost, $salt, $key] = derivedHash.split('.') + } catch (e) { + return reject(new ParseError(derivedHash, e)) + } + if (!$cost || !$salt || !$key) { + return reject(new ParseError(derivedHash)) + } + var cost, salt, key + try { + cost = 1 << Number.parseInt($cost, 16) + salt = Buffer.from($salt, 'base64url') + key = Buffer.from($key, 'base64url') + } catch (e) { + return reject(new ParseError(derivedHash, e)) + } + scrypt(password.normalize(), salt, key.length, { cost }, (err, newKey) => { + if (err) reject(err) + else resolve(timingSafeEqual(key, newKey)) }) + }) } /** * Like `verify()`, but synchronous. - * + * * @param {string} derivedHash The hash as derived and encoded by the above * hash() function * @param {string} password The plaintext password to compare @@ -198,27 +218,32 @@ function verify(derivedHash, password) { * @returns {Promise} whether the password matches */ function verifySync(derivedHash, password) { - var $cost, $salt, $key - try { - [$cost, $salt, $key] = derivedHash.split('.') - } catch (e) { - throw new ParseError(derivedHash, e) - } - if (!$cost || !$salt || !$key) { - throw new ParseError(derivedHash) - } - var cost, salt, key - try { - cost = 1 << Number.parseInt($cost, 16) - salt = Buffer.from($salt, 'base64url') - key = Buffer.from($key, 'base64url') - } catch (e) { - throw new ParseError(derivedHash, e) - } - const newKey = scryptSync(password.normalize(), salt, key.length, { cost }) - return timingSafeEqual(key, newKey) + var $cost, $salt, $key + try { + ;[$cost, $salt, $key] = derivedHash.split('.') + } catch (e) { + throw new ParseError(derivedHash, e) + } + if (!$cost || !$salt || !$key) { + throw new ParseError(derivedHash) + } + var cost, salt, key + try { + cost = 1 << Number.parseInt($cost, 16) + salt = Buffer.from($salt, 'base64url') + key = Buffer.from($key, 'base64url') + } catch (e) { + throw new ParseError(derivedHash, e) + } + const newKey = scryptSync(password.normalize(), salt, key.length, { cost }) + return timingSafeEqual(key, newKey) } module.exports = { - hash, hashSync, verify, verifySync, ParseError, InvalidOptions -} \ No newline at end of file + hash, + hashSync, + verify, + verifySync, + ParseError, + InvalidOptions, +} diff --git a/package.json b/package.json index 2b778b7..73d4778 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,12 @@ "devDependencies": { "chai": "^4.3.8", "chai-as-promised": "^7.1.1", - "mocha": "^10.2.0" + "mocha": "^10.2.0", + "prettier": "^3.0.3" + }, + "prettier": { + "arrowParens": "avoid", + "singleQuote": true, + "semi": false } } diff --git a/test/scrypt.test.js b/test/scrypt.test.js index 954ea39..c34848c 100644 --- a/test/scrypt.test.js +++ b/test/scrypt.test.js @@ -5,177 +5,224 @@ const { expect } = chai const scrypt = require('..') describe('hashing and verification', function () { - it('works', async function () { - const pass = 'b4dpa$s' - const derived = await scrypt.hash(pass) - const [cost, salt, key] = derived.split('.') - cost.should.eq('e') - salt.length.should.eq(32) - key.length.should.eq(86) - Buffer.from(key, 'base64url').length.should.eq(64) - scrypt.verify(derived, pass).should.eventually.be.true - }) + it('works', async function () { + const pass = 'b4dpa$s' + const derived = await scrypt.hash(pass) + const [cost, salt, key] = derived.split('.') + cost.should.eq('e') + salt.length.should.eq(32) + key.length.should.eq(86) + Buffer.from(key, 'base64url').length.should.eq(64) + scrypt.verify(derived, pass).should.eventually.be.true + }) }) describe('hashSync() and sync verification', function () { - it('works', function () { - const pass = 'b4dpa$s' - const derived = scrypt.hashSync(pass) - const [cost, salt, key] = derived.split('.') - cost.should.eq('e') - salt.length.should.eq(32) - key.length.should.eq(86) - Buffer.from(key, 'base64url').length.should.eq(64) - scrypt.verifySync(derived, pass).should.be.true - }) + it('works', function () { + const pass = 'b4dpa$s' + const derived = scrypt.hashSync(pass) + const [cost, salt, key] = derived.split('.') + cost.should.eq('e') + salt.length.should.eq(32) + key.length.should.eq(86) + Buffer.from(key, 'base64url').length.should.eq(64) + scrypt.verifySync(derived, pass).should.be.true + }) }) describe('hash() error cases', function () { - it('throws an error when it receives an invalid cost value', async function () { - scrypt.hash('bad password', { cost: 2.3 }).should.be.rejectedWith(scrypt.InvalidOptions) - scrypt.hash('bad password', { cost: 2 /* too small */ }).should.be.rejectedWith(scrypt.InvalidOptions) - }) - it('throws an error when it receives an invalid keyLength value', async function () { - scrypt.hash('bad password', { keyLength: 2.3 }).should.be.rejectedWith(scrypt.InvalidOptions) - }) - it('throws an error when it receives an invalid saltLength value', async function () { - scrypt.hash('bad password', { saltLength: 2.3 }).should.be.rejectedWith(scrypt.InvalidOptions) - scrypt.hash('bad password', { saltLength: 15 /* too small */ }).should.be.rejectedWith(scrypt.InvalidOptions) - }) - it('throws an error when the password is not the right thing', function () { - scrypt.hash(undefined).should.eventually.be.rejected - scrypt.hash(null).should.eventually.be.rejected - scrypt.hash(123.4).should.eventually.be.rejected - scrypt.hash(NaN).should.eventually.be.rejected - scrypt.hash(['some', 'arbitrary', 'values']).should.eventually.be.rejected - scrypt.hash({ some: 'arbitrary values' }).should.eventually.be.rejected - scrypt.hash(Symbol('something else')).should.eventually.be.rejected - scrypt.hash(() => "no, you can't do this either").should.eventually.be.rejected - }) + it('throws an error when it receives an invalid cost value', async function () { + scrypt + .hash('bad password', { cost: 2.3 }) + .should.be.rejectedWith(scrypt.InvalidOptions) + scrypt + .hash('bad password', { cost: 2 /* too small */ }) + .should.be.rejectedWith(scrypt.InvalidOptions) + }) + it('throws an error when it receives an invalid keyLength value', async function () { + scrypt + .hash('bad password', { keyLength: 2.3 }) + .should.be.rejectedWith(scrypt.InvalidOptions) + }) + it('throws an error when it receives an invalid saltLength value', async function () { + scrypt + .hash('bad password', { saltLength: 2.3 }) + .should.be.rejectedWith(scrypt.InvalidOptions) + scrypt + .hash('bad password', { saltLength: 15 /* too small */ }) + .should.be.rejectedWith(scrypt.InvalidOptions) + }) + it('throws an error when the password is not the right thing', function () { + scrypt.hash(undefined).should.be.rejected + scrypt.hash(null).should.be.rejected + scrypt.hash(123.4).should.be.rejected + scrypt.hash(NaN).should.be.rejected + scrypt.hash(['some', 'arbitrary', 'values']).should.be.rejected + scrypt.hash({ some: 'arbitrary values' }).should.be.rejected + scrypt.hash(Symbol('something else')).should.be.rejected + scrypt.hash(() => "no, you can't do this either").should.be.rejected + }) }) describe('hashSync() error cases', function () { - it('throws an error when it receives an invalid cost value', async function () { - scrypt.hashSync.bind(undefined, 'bad password', { cost: 2.3 }).should.throw(scrypt.InvalidOptions) - scrypt.hashSync.bind(undefined, 'bad password', { cost: 2 /* too small */ }).should.throw(scrypt.InvalidOptions) - }) - it('throws an error when it receives an invalid keyLength value', async function () { - scrypt.hashSync.bind(undefined, 'bad password', { keyLength: 2.3 }).should.throw(scrypt.InvalidOptions) - }) - it('throws an error when it receives an invalid saltLength value', async function () { - scrypt.hashSync.bind(undefined, 'bad password', { saltLength: 2.3 }).should.throw(scrypt.InvalidOptions) - scrypt.hashSync.bind(undefined, 'bad password', { saltLength: 15 }).should.throw(scrypt.InvalidOptions) - }) - it('throws an error when the password is not the right thing', function () { - scrypt.hashSync.bind(undefined, undefined).should.throw() - scrypt.hashSync.bind(undefined, null).should.throw() - scrypt.hashSync.bind(undefined, 123.4).should.throw() - scrypt.hashSync.bind(undefined, NaN).should.throw() - scrypt.hashSync.bind(undefined, ['some', 'arbitrary', 'values']).should.throw() - scrypt.hashSync.bind(undefined, { some: 'arbitrary values' }).should.throw() - scrypt.hashSync.bind(undefined, Symbol('something else')).should.throw() - scrypt.hashSync.bind(undefined, () => "no, you can't do this either").should.throw() - }) + it('throws an error when it receives an invalid cost value', async function () { + scrypt.hashSync + .bind(undefined, 'bad password', { cost: 2.3 }) + .should.throw(scrypt.InvalidOptions) + scrypt.hashSync + .bind(undefined, 'bad password', { cost: 2 /* too small */ }) + .should.throw(scrypt.InvalidOptions) + }) + it('throws an error when it receives an invalid keyLength value', async function () { + scrypt.hashSync + .bind(undefined, 'bad password', { keyLength: 2.3 }) + .should.throw(scrypt.InvalidOptions) + }) + it('throws an error when it receives an invalid saltLength value', async function () { + scrypt.hashSync + .bind(undefined, 'bad password', { saltLength: 2.3 }) + .should.throw(scrypt.InvalidOptions) + scrypt.hashSync + .bind(undefined, 'bad password', { saltLength: 15 }) + .should.throw(scrypt.InvalidOptions) + }) + it('throws an error when the password is not the right thing', function () { + scrypt.hashSync.bind(undefined, undefined).should.throw() + scrypt.hashSync.bind(undefined, null).should.throw() + scrypt.hashSync.bind(undefined, 123.4).should.throw() + scrypt.hashSync.bind(undefined, NaN).should.throw() + scrypt.hashSync + .bind(undefined, ['some', 'arbitrary', 'values']) + .should.throw() + scrypt.hashSync.bind(undefined, { some: 'arbitrary values' }).should.throw() + scrypt.hashSync.bind(undefined, Symbol('something else')).should.throw() + scrypt.hashSync + .bind(undefined, () => "no, you can't do this either") + .should.throw() + }) }) describe('verify() error cases', function () { - it('throws an error when the password is not the right thing', function () { - const validHash = 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' - scrypt.verify(validHash, undefined).should.eventually.be.rejected - scrypt.verify(validHash, null).should.eventually.be.rejected - scrypt.verify(validHash, 123.4).should.eventually.be.rejected - scrypt.verify(validHash, NaN).should.eventually.be.rejected - scrypt.verify(validHash, ['some', 'arbitrary', 'values']).should.eventually.be.rejected - scrypt.verify(validHash, { some: 'arbitrary values' }).should.eventually.be.rejected - scrypt.verify(validHash, Symbol('something else')).should.eventually.be.rejected - scrypt.verify(validHash, () => "no, you can't do this either").should.eventually.be.rejected - }) - it('throws an error when the hash is not the expected format', async function () { - const validHash = 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' - const hashParts = validHash.split('.') - async function checkThrows(value, cause) { - let thrown - try { - await scrypt.verify(value, 'bad password') - thrown = false - } catch (e) { - thrown = true - e.should.be.an.instanceof(scrypt.ParseError) - expect(e.malformedString).to.eq(value) - if (cause) - expect(e.cause).to.be.an.instanceof(cause) - else - expect(e.cause).to.be.undefined - } - thrown.should.be.true - } - await checkThrows(undefined, TypeError) - await checkThrows(1234, TypeError) - await checkThrows(null, TypeError) - await checkThrows([1, 2, 3], TypeError) - scrypt.verify({ - // This is technically valid, why try to prevent it? 🤷 - split: () => hashParts - }, 'bad password').should.eventually.be.true - await checkThrows({}, TypeError) + it('throws an error when the password is not the right thing', function () { + const validHash = + 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' + scrypt.verify(validHash, undefined).should.be.rejected + scrypt.verify(validHash, null).should.be.rejected + scrypt.verify(validHash, 123.4).should.be.rejected + scrypt.verify(validHash, NaN).should.be.rejected + scrypt.verify(validHash, ['some', 'arbitrary', 'values']).should.be.rejected + scrypt.verify(validHash, { some: 'arbitrary values' }).should.be.rejected + scrypt.verify(validHash, Symbol('something else')).should.be.rejected + scrypt.verify(validHash, () => "no, you can't do this either").should.be + .rejected + scrypt.verify(validHash, Promise.resolve("no, you can't do this either")) + .should.be.rejected + }) + it('throws an error when the hash is not the expected format', async function () { + const validHash = + 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' + const hashParts = validHash.split('.') + async function checkThrows(value, cause) { + let thrown + try { + await scrypt.verify(value, 'bad password') + thrown = false + } catch (e) { + thrown = true + e.should.be.an.instanceof(scrypt.ParseError) + expect(e.malformedString).to.eq(value) + if (cause) expect(e.cause).to.be.an.instanceof(cause) + else expect(e.cause).to.be.undefined + } + thrown.should.be.true + } + await checkThrows(undefined, TypeError) + await checkThrows(1234, TypeError) + await checkThrows(null, TypeError) + await checkThrows([1, 2, 3], TypeError) + scrypt.verify( + { + // This is technically valid, why try to prevent it? 🤷 + split: () => hashParts, + }, + 'bad password', + ).should.eventually.be.true + await checkThrows({}, TypeError) - await checkThrows(validHash.substring(1)) // No cost - // no salt - await checkThrows('e..' + hashParts[2]) - // no key - await checkThrows(hashParts[0] + '.' + hashParts[1]) - await checkThrows('...') - await checkThrows('') - }) + await checkThrows(validHash.substring(1)) // No cost + // no salt + await checkThrows('e..' + hashParts[2]) + // no key + await checkThrows(hashParts[0] + '.' + hashParts[1]) + await checkThrows('...') + await checkThrows('') + }) }) describe('verifySync() error cases', function () { - it('throws an error when the password is not the right thing', function () { - const validHash = 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' - scrypt.verifySync.bind(undefined, validHash, undefined).should.throw() - scrypt.verifySync.bind(undefined, validHash, null).should.throw() - scrypt.verifySync.bind(undefined, validHash, 123.4).should.throw() - scrypt.verifySync.bind(undefined, validHash, NaN).should.throw() - scrypt.verifySync.bind(undefined, validHash, ['some', 'arbitrary', 'values']).should.throw() - scrypt.verifySync.bind(undefined, validHash, { some: 'arbitrary values' }).should.throw() - scrypt.verifySync.bind(undefined, validHash, Symbol('something else')).should.throw() - scrypt.verifySync.bind(undefined, validHash, () => "no, you can't do this either").should.throw() - scrypt.verifySync.bind(undefined, validHash, Promise.resolve("no, you can't do this either")).should.throw() - }) - it('throws an error when the hash is not the expected format', function () { - const validHash = 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' - const hashParts = validHash.split('.') - function checkThrows(value, cause) { - let thrown - try { - scrypt.verifySync(value, 'bad password') - thrown = false - } catch (e) { - thrown = true - e.should.be.an.instanceof(scrypt.ParseError) - expect(e.malformedString).to.eq(value) - if (cause) - expect(e.cause).to.be.an.instanceof(cause) - else - expect(e.cause).to.be.undefined - } - thrown.should.be.true - } - checkThrows(undefined, TypeError) - checkThrows(1234, TypeError) - checkThrows(null, TypeError) - checkThrows([1, 2, 3], TypeError) - scrypt.verify({ - // This is technically valid, why try to prevent it? 🤷 - split: () => hashParts - }, 'bad password').should.eventually.be.true - checkThrows({}, TypeError) + it('throws an error when the password is not the right thing', function () { + const validHash = + 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' + scrypt.verifySync.bind(undefined, validHash, undefined).should.throw() + scrypt.verifySync.bind(undefined, validHash, null).should.throw() + scrypt.verifySync.bind(undefined, validHash, 123.4).should.throw() + scrypt.verifySync.bind(undefined, validHash, NaN).should.throw() + scrypt.verifySync + .bind(undefined, validHash, ['some', 'arbitrary', 'values']) + .should.throw() + scrypt.verifySync + .bind(undefined, validHash, { some: 'arbitrary values' }) + .should.throw() + scrypt.verifySync + .bind(undefined, validHash, Symbol('something else')) + .should.throw() + scrypt.verifySync + .bind(undefined, validHash, () => "no, you can't do this either") + .should.throw() + scrypt.verifySync + .bind( + undefined, + validHash, + Promise.resolve("no, you can't do this either"), + ) + .should.throw() + }) + it('throws an error when the hash is not the expected format', function () { + const validHash = + 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' + const hashParts = validHash.split('.') + function checkThrows(value, cause) { + let thrown + try { + scrypt.verifySync(value, 'bad password') + thrown = false + } catch (e) { + thrown = true + e.should.be.an.instanceof(scrypt.ParseError) + expect(e.malformedString).to.eq(value) + if (cause) expect(e.cause).to.be.an.instanceof(cause) + else expect(e.cause).to.be.undefined + } + thrown.should.be.true + } + checkThrows(undefined, TypeError) + checkThrows(1234, TypeError) + checkThrows(null, TypeError) + checkThrows([1, 2, 3], TypeError) + scrypt.verify( + { + // This is technically valid, why try to prevent it? 🤷 + split: () => hashParts, + }, + 'bad password', + ).should.eventually.be.true + checkThrows({}, TypeError) - checkThrows(validHash.substring(1)) // No cost - // no salt - checkThrows('e..' + hashParts[2]) - // no key - checkThrows(hashParts[0] + '.' + hashParts[1]) - checkThrows('...') - checkThrows('') - }) -}) \ No newline at end of file + checkThrows(validHash.substring(1)) // No cost + // no salt + checkThrows('e..' + hashParts[2]) + // no key + checkThrows(hashParts[0] + '.' + hashParts[1]) + checkThrows('...') + checkThrows('') + }) +}) diff --git a/yarn.lock b/yarn.lock index 06fac9a..3842446 100644 --- a/yarn.lock +++ b/yarn.lock @@ -444,6 +444,11 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +prettier@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"