index.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*!
  2. * csrf
  3. * Copyright(c) 2014 Jonathan Ong
  4. * Copyright(c) 2015 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict'
  8. /**
  9. * Module dependencies.
  10. * @private
  11. */
  12. var rndm = require('rndm')
  13. var uid = require('uid-safe')
  14. var compare = require('tsscmp')
  15. var crypto = require('crypto')
  16. /**
  17. * Module variables.
  18. * @private
  19. */
  20. var EQUAL_GLOBAL_REGEXP = /=/g
  21. var PLUS_GLOBAL_REGEXP = /\+/g
  22. var SLASH_GLOBAL_REGEXP = /\//g
  23. /**
  24. * Module exports.
  25. * @public
  26. */
  27. module.exports = Tokens
  28. /**
  29. * Token generation/verification class.
  30. *
  31. * @param {object} [options]
  32. * @param {number} [options.saltLength=8] The string length of the salt
  33. * @param {number} [options.secretLength=18] The byte length of the secret key
  34. * @public
  35. */
  36. function Tokens (options) {
  37. if (!(this instanceof Tokens)) {
  38. return new Tokens(options)
  39. }
  40. var opts = options || {}
  41. var saltLength = opts.saltLength !== undefined
  42. ? opts.saltLength
  43. : 8
  44. if (typeof saltLength !== 'number' || !isFinite(saltLength) || saltLength < 1) {
  45. throw new TypeError('option saltLength must be finite number > 1')
  46. }
  47. var secretLength = opts.secretLength !== undefined
  48. ? opts.secretLength
  49. : 18
  50. if (typeof secretLength !== 'number' || !isFinite(secretLength) || secretLength < 1) {
  51. throw new TypeError('option secretLength must be finite number > 1')
  52. }
  53. this.saltLength = saltLength
  54. this.secretLength = secretLength
  55. }
  56. /**
  57. * Create a new CSRF token.
  58. *
  59. * @param {string} secret The secret for the token.
  60. * @public
  61. */
  62. Tokens.prototype.create = function create (secret) {
  63. if (!secret || typeof secret !== 'string') {
  64. throw new TypeError('argument secret is required')
  65. }
  66. return this._tokenize(secret, rndm(this.saltLength))
  67. }
  68. /**
  69. * Create a new secret key.
  70. *
  71. * @param {function} [callback]
  72. * @public
  73. */
  74. Tokens.prototype.secret = function secret (callback) {
  75. return uid(this.secretLength, callback)
  76. }
  77. /**
  78. * Create a new secret key synchronously.
  79. * @public
  80. */
  81. Tokens.prototype.secretSync = function secretSync () {
  82. return uid.sync(this.secretLength)
  83. }
  84. /**
  85. * Tokenize a secret and salt.
  86. * @private
  87. */
  88. Tokens.prototype._tokenize = function tokenize (secret, salt) {
  89. return salt + '-' + hash(salt + '-' + secret)
  90. }
  91. /**
  92. * Verify if a given token is valid for a given secret.
  93. *
  94. * @param {string} secret
  95. * @param {string} token
  96. * @public
  97. */
  98. Tokens.prototype.verify = function verify (secret, token) {
  99. if (!secret || typeof secret !== 'string') {
  100. return false
  101. }
  102. if (!token || typeof token !== 'string') {
  103. return false
  104. }
  105. var index = token.indexOf('-')
  106. if (index === -1) {
  107. return false
  108. }
  109. var salt = token.substr(0, index)
  110. var expected = this._tokenize(secret, salt)
  111. return compare(token, expected)
  112. }
  113. /**
  114. * Hash a string with SHA1, returning url-safe base64
  115. * @param {string} str
  116. * @private
  117. */
  118. function hash (str) {
  119. return crypto
  120. .createHash('sha1')
  121. .update(str, 'ascii')
  122. .digest('base64')
  123. .replace(PLUS_GLOBAL_REGEXP, '-')
  124. .replace(SLASH_GLOBAL_REGEXP, '_')
  125. .replace(EQUAL_GLOBAL_REGEXP, '')
  126. }