Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add algorithm-option, set default hash algorithm to sha256 #45

Merged
merged 3 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
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 `sha1`.
Uzlopak marked this conversation as resolved.
Show resolved Hide resolved

##### 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 sha1
Uzlopak marked this conversation as resolved.
Show resolved Hide resolved
*/
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