/*! * csrf * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * Module dependencies. * @private */ var rndm = require('rndm') var uid = require('uid-safe') var compare = require('tsscmp') var crypto = require('crypto') /** * Module variables. * @private */ var EQUAL_GLOBAL_REGEXP = /=/g var PLUS_GLOBAL_REGEXP = /\+/g var SLASH_GLOBAL_REGEXP = /\//g /** * Module exports. * @public */ module.exports = Tokens /** * Token generation/verification class. * * @param {object} [options] * @param {number} [options.saltLength=8] The string length of the salt * @param {number} [options.secretLength=18] The byte length of the secret key * @public */ function Tokens (options) { if (!(this instanceof Tokens)) { return new Tokens(options) } var opts = options || {} var saltLength = opts.saltLength !== undefined ? opts.saltLength : 8 if (typeof saltLength !== 'number' || !isFinite(saltLength) || saltLength < 1) { throw new TypeError('option saltLength must be finite number > 1') } var secretLength = opts.secretLength !== undefined ? opts.secretLength : 18 if (typeof secretLength !== 'number' || !isFinite(secretLength) || secretLength < 1) { throw new TypeError('option secretLength must be finite number > 1') } this.saltLength = saltLength this.secretLength = secretLength } /** * Create a new CSRF token. * * @param {string} secret The secret for the token. * @public */ Tokens.prototype.create = function create (secret) { if (!secret || typeof secret !== 'string') { throw new TypeError('argument secret is required') } return this._tokenize(secret, rndm(this.saltLength)) } /** * Create a new secret key. * * @param {function} [callback] * @public */ Tokens.prototype.secret = function secret (callback) { return uid(this.secretLength, callback) } /** * Create a new secret key synchronously. * @public */ Tokens.prototype.secretSync = function secretSync () { return uid.sync(this.secretLength) } /** * Tokenize a secret and salt. * @private */ Tokens.prototype._tokenize = function tokenize (secret, salt) { return salt + '-' + hash(salt + '-' + secret) } /** * Verify if a given token is valid for a given secret. * * @param {string} secret * @param {string} token * @public */ Tokens.prototype.verify = function verify (secret, token) { if (!secret || typeof secret !== 'string') { return false } if (!token || typeof token !== 'string') { return false } var index = token.indexOf('-') if (index === -1) { return false } var salt = token.substr(0, index) var expected = this._tokenize(secret, salt) return compare(token, expected) } /** * Hash a string with SHA1, returning url-safe base64 * @param {string} str * @private */ function hash (str) { return crypto .createHash('sha1') .update(str, 'ascii') .digest('base64') .replace(PLUS_GLOBAL_REGEXP, '-') .replace(SLASH_GLOBAL_REGEXP, '_') .replace(EQUAL_GLOBAL_REGEXP, '') }