Prettier+some nitpicks

This commit is contained in:
D. Scott Boggs 2023-09-26 08:45:29 -04:00
parent 8c18fc4f92
commit 1432f43035
5 changed files with 369 additions and 287 deletions

View file

@ -60,7 +60,6 @@ module.exports = (sequelize, DataTypes) => {
sequelize,
tableName: 'Users',
modelName: 'User',
underscored: true,
},
)
return User

View file

@ -6,7 +6,12 @@
* 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
@ -32,7 +37,9 @@ class ParseError extends Error {
* @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`)
super(
`error parsing ${malformedString}, expected hash encoded by @techworkers/scrypt-wrapper`,
)
this.cause = cause
this.malformedString = malformedString
}
@ -50,7 +57,9 @@ 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}`)
super(
`invalid options were received:\n\tcost: ${options.cost}\n\tkeyLength: ${options.keyLength}\n\tsaltLength: ${options.saltLength}`,
)
this.options = options
}
@ -63,9 +72,9 @@ class InvalidOptions extends Error {
const { cost, keyLength, saltLength } = options
// Cost validation
if (
!(typeof cost === 'number' || cost instanceof Number)
|| cost < 13
|| !Number.isInteger(cost)
!(typeof cost === 'number' || cost instanceof Number) ||
cost < 13 ||
!Number.isInteger(cost)
) {
const err = new InvalidOptions(options)
if (reject) reject(err)
@ -73,8 +82,8 @@ class InvalidOptions extends Error {
}
// keyLength validation
if (
!(typeof keyLength === 'number' || keyLength instanceof Number)
|| !Number.isInteger(keyLength)
!(typeof keyLength === 'number' || keyLength instanceof Number) ||
!Number.isInteger(keyLength)
) {
const err = new InvalidOptions(options)
if (reject) reject(err)
@ -82,9 +91,9 @@ class InvalidOptions extends Error {
}
// saltLength validation
if (
!(typeof saltLength === 'number' || saltLength instanceof Number)
|| saltLength < 16 /* (recommended minimum) */
|| !Number.isInteger(saltLength)
!(typeof saltLength === 'number' || saltLength instanceof Number) ||
saltLength < 16 /* (recommended minimum) */ ||
!Number.isInteger(saltLength)
) {
const err = new InvalidOptions(options)
if (reject) reject(err)
@ -124,10 +133,16 @@ function hash(password, options) {
InvalidOptions.validateOptions({ cost, keyLength, saltLength }, reject)
let $hash = cost.toString(16) + '.'
$hash += salt.toString('base64url') + '.'
scrypt(password.normalize(), salt, keyLength, { cost: $cost }, (err, key) => {
scrypt(
password.normalize(),
salt,
keyLength,
{ cost: $cost },
(err, key) => {
if (err) reject(err)
else resolve($hash + key.toString('base64url'))
})
},
)
})
}
@ -148,7 +163,12 @@ function hashSync(password, options) {
const $cost = 1 << cost
let $hash = cost.toString(16) + '.'
$hash += salt.toString('base64url') + '.'
return $hash + scryptSync(password.normalize(), salt, keyLength, { cost: $cost }).toString('base64url')
return (
$hash +
scryptSync(password.normalize(), salt, keyLength, { cost: $cost }).toString(
'base64url',
)
)
}
/**
@ -166,7 +186,7 @@ function verify(derivedHash, password) {
return new Promise((resolve, reject) => {
var $cost, $salt, $key
try {
[$cost, $salt, $key] = derivedHash.split('.')
;[$cost, $salt, $key] = derivedHash.split('.')
} catch (e) {
return reject(new ParseError(derivedHash, e))
}
@ -200,7 +220,7 @@ function verify(derivedHash, password) {
function verifySync(derivedHash, password) {
var $cost, $salt, $key
try {
[$cost, $salt, $key] = derivedHash.split('.')
;[$cost, $salt, $key] = derivedHash.split('.')
} catch (e) {
throw new ParseError(derivedHash, e)
}
@ -220,5 +240,10 @@ function verifySync(derivedHash, password) {
}
module.exports = {
hash, hashSync, verify, verifySync, ParseError, InvalidOptions
hash,
hashSync,
verify,
verifySync,
ParseError,
InvalidOptions,
}

View file

@ -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
}
}

View file

@ -31,66 +31,95 @@ describe('hashSync() and sync verification', function () {
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)
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)
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)
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
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)
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)
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)
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, { 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()
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
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 validHash =
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
const hashParts = validHash.split('.')
async function checkThrows(value, cause) {
let thrown
@ -101,10 +130,8 @@ describe('verify() error cases', function () {
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
if (cause) expect(e.cause).to.be.an.instanceof(cause)
else expect(e.cause).to.be.undefined
}
thrown.should.be.true
}
@ -112,10 +139,13 @@ describe('verify() error cases', function () {
await checkThrows(1234, TypeError)
await checkThrows(null, TypeError)
await checkThrows([1, 2, 3], TypeError)
scrypt.verify({
scrypt.verify(
{
// This is technically valid, why try to prevent it? 🤷
split: () => hashParts
}, 'bad password').should.eventually.be.true
split: () => hashParts,
},
'bad password',
).should.eventually.be.true
await checkThrows({}, TypeError)
await checkThrows(validHash.substring(1)) // No cost
@ -130,19 +160,35 @@ describe('verify() error cases', function () {
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'
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()
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 validHash =
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
const hashParts = validHash.split('.')
function checkThrows(value, cause) {
let thrown
@ -153,10 +199,8 @@ describe('verifySync() error cases', function () {
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
if (cause) expect(e.cause).to.be.an.instanceof(cause)
else expect(e.cause).to.be.undefined
}
thrown.should.be.true
}
@ -164,10 +208,13 @@ describe('verifySync() error cases', function () {
checkThrows(1234, TypeError)
checkThrows(null, TypeError)
checkThrows([1, 2, 3], TypeError)
scrypt.verify({
scrypt.verify(
{
// This is technically valid, why try to prevent it? 🤷
split: () => hashParts
}, 'bad password').should.eventually.be.true
split: () => hashParts,
},
'bad password',
).should.eventually.be.true
checkThrows({}, TypeError)
checkThrows(validHash.substring(1)) // No cost

View file

@ -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"