TokenGenerator.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /* eslint-disable
  2. n/handle-callback-err,
  3. max-len,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const crypto = require('crypto')
  14. const V1Api = require('../V1/V1Api')
  15. const Features = require('../../infrastructure/Features')
  16. const Async = require('async')
  17. const { promisify } = require('util')
  18. // (From Overleaf `random_token.rb`)
  19. // Letters (not numbers! see generate_token) used in tokens. They're all
  20. // consonants, to avoid embarassing words (I can't think of any that use only
  21. // a y), and lower case "l" is omitted, because in many fonts it is
  22. // indistinguishable from an upper case "I" (and sometimes even the number 1).
  23. const TOKEN_LOWERCASE_ALPHA = 'bcdfghjkmnpqrstvwxyz'
  24. const TOKEN_NUMERICS = '123456789'
  25. const TOKEN_ALPHANUMERICS =
  26. TOKEN_LOWERCASE_ALPHA + TOKEN_LOWERCASE_ALPHA.toUpperCase() + TOKEN_NUMERICS
  27. // This module mirrors the token generation in Overleaf (`random_token.rb`),
  28. // for the purposes of implementing token-based project access, like the
  29. // 'unlisted-projects' feature in Overleaf
  30. const TokenGenerator = {
  31. _randomString(length, alphabet) {
  32. const result = crypto
  33. .randomBytes(length)
  34. .toJSON()
  35. .data.map(b => alphabet[b % alphabet.length])
  36. .join('')
  37. return result
  38. },
  39. // Generate a 12-char token with only characters from TOKEN_LOWERCASE_ALPHA,
  40. // suitable for use as a read-only token for a project
  41. readOnlyToken() {
  42. return TokenGenerator._randomString(12, TOKEN_LOWERCASE_ALPHA)
  43. },
  44. // Generate a longer token, with a numeric prefix,
  45. // suitable for use as a read-and-write token for a project
  46. readAndWriteToken() {
  47. const numerics = TokenGenerator._randomString(10, TOKEN_NUMERICS)
  48. const token = TokenGenerator._randomString(12, TOKEN_LOWERCASE_ALPHA)
  49. const fullToken = `${numerics}${token}`
  50. return { token: fullToken, numericPrefix: numerics }
  51. },
  52. generateReferralId() {
  53. return TokenGenerator._randomString(16, TOKEN_ALPHANUMERICS)
  54. },
  55. generateUniqueReadOnlyToken(callback) {
  56. if (callback == null) {
  57. callback = function () {}
  58. }
  59. return Async.retry(
  60. 10,
  61. function (cb) {
  62. const token = TokenGenerator.readOnlyToken()
  63. if (!Features.hasFeature('overleaf-integration')) {
  64. return cb(null, token)
  65. }
  66. return V1Api.request(
  67. {
  68. url: `/api/v1/overleaf/docs/read_token/${token}/exists`,
  69. json: true,
  70. },
  71. function (err, response, body) {
  72. if (err != null) {
  73. return cb(err)
  74. }
  75. if (response.statusCode !== 200) {
  76. return cb(
  77. new Error(
  78. `non-200 response from v1 read-token-exists api: ${response.statusCode}`
  79. )
  80. )
  81. }
  82. if (body.exists === true) {
  83. return cb(new Error(`token already exists in v1: ${token}`))
  84. } else {
  85. return cb(null, token)
  86. }
  87. }
  88. )
  89. },
  90. callback
  91. )
  92. },
  93. }
  94. TokenGenerator.promises = {
  95. generateUniqueReadOnlyToken: promisify(
  96. TokenGenerator.generateUniqueReadOnlyToken
  97. ),
  98. }
  99. module.exports = TokenGenerator