const chai = require('chai') chai.use(require('chai-as-promised')) chai.should() 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) await 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 }) }) describe('hash() error cases', function () { it('throws an error when it receives an invalid cost value', async function () { await scrypt .hash('bad password', { cost: 2.3 }) .should.be.rejectedWith(scrypt.InvalidOptions) await 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 () { await 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 () { await scrypt .hash('bad password', { saltLength: 2.3 }) .should.be.rejectedWith(scrypt.InvalidOptions) await 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', async function () { await Promise.all([ 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() }) }) describe('verify() error cases', function () { it('throws an error when the password is not the right thing', async function () { const validHash = 'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g' await Promise.all([ 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) await 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('') }) }) 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) checkThrows(validHash.substring(1)) // No cost // no salt checkThrows('e..' + hashParts[2]) // no key checkThrows(hashParts[0] + '.' + hashParts[1]) checkThrows('...') checkThrows('') }) }) describe('normalization of unicode characters', function () { it('works, async', async () => { const angstromChar = '\xC5ngstrom' const composed = 'A\u030Angstrom' const withRingAbove = '\u212Bngstrom' let hash = await scrypt.hash(angstromChar) const tests = [] tests.push(scrypt.verify(hash, composed).should.eventually.be.true) tests.push(scrypt.verify(hash, withRingAbove).should.eventually.be.true) hash = await scrypt.hash(composed) tests.push(scrypt.verify(hash, withRingAbove).should.eventually.be.true) await Promise.all(tests) }) it('works, sync', () => { const angstromChar = '\xC5ngstrom' const composed = 'A\u030Angstrom' const withRingAbove = '\u212Bngstrom' let hash = scrypt.hashSync(angstromChar) scrypt.verifySync(hash, composed).should.be.true scrypt.verifySync(hash, withRingAbove).should.be.true hash = scrypt.hashSync(composed) scrypt.verifySync(hash, withRingAbove).should.be.true }) })