60 lines
1.7 KiB
JavaScript
60 lines
1.7 KiB
JavaScript
|
|
||
|
var rndm = require('rndm')
|
||
|
var scmp = require('scmp')
|
||
|
var uid = require('uid-safe')
|
||
|
var crypto = require('crypto')
|
||
|
var escape = require('base64-url').escape
|
||
|
|
||
|
module.exports = csrfTokens
|
||
|
|
||
|
function csrfTokens(options) {
|
||
|
options = options || {}
|
||
|
|
||
|
// adjustable lengths
|
||
|
var secretLength = options.secretLength || 18 // the longer the better
|
||
|
var saltLength = options.saltLength || 8 // doesn't need to be long
|
||
|
|
||
|
// convert a secret + a salt to a token
|
||
|
// this does NOT have to be cryptographically secure, so we don't use HMAC,
|
||
|
// and we use sha1 because sha256 is unnecessarily long for cookies and stuff
|
||
|
var tokenize = options.tokenize || csrfTokens.tokenize
|
||
|
|
||
|
return {
|
||
|
// create a secret key
|
||
|
// this __should__ be cryptographically secure,
|
||
|
// but generally client's can't/shouldn't-be-able-to access this so it really doesn't matter.
|
||
|
secret: function secret(cb) {
|
||
|
return uid(secretLength, cb)
|
||
|
},
|
||
|
|
||
|
// a sync version of secret()
|
||
|
secretSync: function secretSync() {
|
||
|
return uid.sync(secretLength)
|
||
|
},
|
||
|
|
||
|
// create a csrf token
|
||
|
create: function create(secret) {
|
||
|
return tokenize(secret, rndm(saltLength))
|
||
|
},
|
||
|
|
||
|
// verify whether a token is valid
|
||
|
verify: function verify(secret, token) {
|
||
|
if (!secret || typeof secret !== 'string') return false
|
||
|
if (!token || typeof token !== 'string') return false
|
||
|
var expected = tokenize(secret, token.split('-')[0])
|
||
|
if (!expected) return false
|
||
|
return scmp(token, expected)
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
csrfTokens.tokenize = function tokenize(secret, salt) {
|
||
|
var hash = escape(crypto
|
||
|
.createHash('sha1')
|
||
|
.update(salt)
|
||
|
.update('-')
|
||
|
.update(secret)
|
||
|
.digest('base64'))
|
||
|
return salt + '-' + hash
|
||
|
}
|