Skip to content

Commit

Permalink
major: add algorithm-option, set default hash algorithm to sha256 (#45)
Browse files Browse the repository at this point in the history
* add algorithm-option

* use sha256 per default

* Apply suggestions from code review

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>
  • Loading branch information
Uzlopak and Eomm authored Sep 23, 2022
1 parent 48b066d commit 3e18fdf
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 11 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ optional and will just use all defaults if missing.

Tokens accepts these properties in the options object.

##### algorithm

The hash-algorithm to generate the token. Defaults to `sha256`.

##### saltLength

The length of the internal salt to use, in characters. Internally, the salt
Expand Down
28 changes: 20 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ function Tokens (options) {

const opts = options || {}

const algorithm = opts.algorithm !== undefined
? opts.algorithm
: 'sha256'

try {
crypto
.createHash(algorithm)
} catch (err) {
throw new TypeError('option algorithm must be a supported hash-algorithm')
}

const saltLength = opts.saltLength !== undefined
? opts.saltLength
: 8
Expand Down Expand Up @@ -60,6 +71,7 @@ function Tokens (options) {
throw new TypeError('option userInfo must be a boolean')
}

this.algorithm = algorithm
this.saltLength = saltLength
this.saltGenerator = saltGenerator(saltLength)
this.secretLength = secretLength
Expand Down Expand Up @@ -88,7 +100,7 @@ Tokens.prototype.create = function create (secret, userInfo) {
}
}

return this._tokenize(secret, this.saltGenerator(), date, userInfo)
return this._tokenize(secret, this.saltGenerator(), date, userInfo, this.algorithm)
}

/**
Expand Down Expand Up @@ -186,7 +198,7 @@ Tokens.prototype.secretSync = Buffer.isEncoding('base64url')
*/

Tokens.prototype._tokenize = Buffer.isEncoding('base64url')
? function _tokenize (secret, salt, date, userInfo) {
? function _tokenize (secret, salt, date, userInfo, algorithm) {
let toHash = ''

if (date !== null) {
Expand All @@ -195,7 +207,7 @@ Tokens.prototype._tokenize = Buffer.isEncoding('base64url')

if (typeof userInfo === 'string') {
toHash += crypto
.createHash('sha1')
.createHash(algorithm)
.update(userInfo)
.digest('base64url')
.replace(MINUS_GLOBAL_REGEXP, '_') + '-'
Expand All @@ -204,11 +216,11 @@ Tokens.prototype._tokenize = Buffer.isEncoding('base64url')
toHash += salt

return toHash + '-' + crypto
.createHash('sha1')
.createHash(algorithm)
.update(toHash + '-' + secret, 'ascii')
.digest('base64url')
}
: function _tokenize (secret, salt, date, userInfo) {
: function _tokenize (secret, salt, date, userInfo, algorithm) {
let toHash = ''

if (date !== null) {
Expand All @@ -217,7 +229,7 @@ Tokens.prototype._tokenize = Buffer.isEncoding('base64url')

if (typeof userInfo === 'string') {
toHash += crypto
.createHash('sha1')
.createHash(algorithm)
.update(userInfo)
.digest('base64')
.replace(PLUS_SLASH_GLOBAL_REGEXP, '_')
Expand All @@ -227,7 +239,7 @@ Tokens.prototype._tokenize = Buffer.isEncoding('base64url')
toHash += salt

return toHash + '-' + crypto
.createHash('sha1')
.createHash(algorithm)
.update(toHash + '-' + secret, 'ascii')
.digest('base64')
.replace(PLUS_GLOBAL_REGEXP, '-')
Expand Down Expand Up @@ -294,7 +306,7 @@ Tokens.prototype.verify = function verify (secret, token, userInfo) {
const salt = token.slice(curIdx, nextIdx)

const actual = Buffer.from(token)
const expected = Buffer.from(this._tokenize(secret, salt, date, userInfo))
const expected = Buffer.from(this._tokenize(secret, salt, date, userInfo, this.algorithm))

// to avoid the exposure if the provided value has the correct length, we call
// timingSafeEqual with the actual value. The additional length check itself is
Expand Down
5 changes: 5 additions & 0 deletions test/constructor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,8 @@ test('Tokens.constructor: instantiating Tokens without new creates still the Tok
t.plan(1)
t.ok(Tokens() instanceof Tokens, true)
})

test('Tokens.constructor: instantiating Tokens with "invalid" for algorithm should throw', t => {
t.plan(1)
t.throws(() => new Tokens({ algorithm: 'invalid' }), new TypeError('option algorithm must be a supported hash-algorithm'))
})
2 changes: 1 addition & 1 deletion test/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test('Tokens.create: should always be the same length', t => {
const secret = new Tokens().secretSync()
const tokenLength = new Tokens().create(secret).length

t.equal(tokenLength, 36)
t.equal(tokenLength, 52)

for (let i = 0; i < 1000; i++) {
t.equal(new Tokens().create(secret).length, tokenLength)
Expand Down
20 changes: 20 additions & 0 deletions test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,23 @@ test('.create() and verify() with user info: should return false for edge case',

t.equal(new Tokens({ userInfo: true }).verify(secret, token, 'foo'), false)
})

test('.create() and verify() with validity: should use by default sha256 as algorithm', t => {
t.plan(2)

const secret = new Tokens().secretSync()
const token = new Tokens({ userInfo: true }).create(secret, 'foobar')

t.equal(token.length, 96)
t.equal(new Tokens({ userInfo: true, algorithm: 'sha256' }).verify(secret, token, 'foobar'), true)
})

test('.create() and verify() with validity: should be able to set sha1 as algorithm', t => {
t.plan(2)

const secret = new Tokens().secretSync()
const token = new Tokens({ userInfo: true, algorithm: 'sha1' }).create(secret, 'foobar')

t.equal(token.length, 64)
t.equal(new Tokens({ userInfo: true, algorithm: 'sha1' }).verify(secret, token, 'foobar'), true)
})
6 changes: 4 additions & 2 deletions test/secret.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ test('Tokens.secret: should handle error, Promise', t => {
crypto: {
randomBytes: (_size, cb) => {
cb(new Error('oh no'))
}
},
createHash: require('crypto').createHash
}
})

Expand All @@ -96,7 +97,8 @@ test('Tokens.secret: should handle error, callback', t => {
crypto: {
randomBytes: (size, cb) => {
cb(new Error('oh no'))
}
},
createHash: require('crypto').createHash
}
})

Expand Down
6 changes: 6 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ interface TokensUserinfo extends TokensBase {
export type SecretCallback = (err: Error | null, secret: string) => void;

export interface Options {
/**
* The algorithm used to generate the token
* @default sha256
*/
algorithm?: string;

/**
* The string length of the salt
*
Expand Down
2 changes: 2 additions & 0 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Tokens();
new Tokens();
Tokens({});
new Tokens({});
Tokens({ algorithm: 'sha1' });
Tokens({ algorithm: 'sha256' });
Tokens({ saltLength: 10 });
Tokens({ secretLength: 10 });
Tokens({ userInfo: true });
Expand Down

0 comments on commit 3e18fdf

Please sign in to comment.