ProjectTokenGenerator.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /* eslint-disable
  2. node/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. // This module mirrors the token generation in Overleaf (`random_token.rb`),
  19. // for the purposes of implementing token-based project access, like the
  20. // 'unlisted-projects' feature in Overleaf
  21. const ProjectTokenGenerator = {
  22. // (From Overleaf `random_token.rb`)
  23. // Letters (not numbers! see generate_token) used in tokens. They're all
  24. // consonants, to avoid embarassing words (I can't think of any that use only
  25. // a y), and lower case "l" is omitted, because in many fonts it is
  26. // indistinguishable from an upper case "I" (and sometimes even the number 1).
  27. TOKEN_ALPHA: 'bcdfghjkmnpqrstvwxyz',
  28. TOKEN_NUMERICS: '123456789',
  29. _randomString(length, alphabet) {
  30. const result = crypto
  31. .randomBytes(length)
  32. .toJSON()
  33. .data.map(b => alphabet[b % alphabet.length])
  34. .join('')
  35. return result
  36. },
  37. // Generate a 12-char token with only characters from TOKEN_ALPHA,
  38. // suitable for use as a read-only token for a project
  39. readOnlyToken() {
  40. return ProjectTokenGenerator._randomString(
  41. 12,
  42. ProjectTokenGenerator.TOKEN_ALPHA
  43. )
  44. },
  45. // Generate a longer token, with a numeric prefix,
  46. // suitable for use as a read-and-write token for a project
  47. readAndWriteToken() {
  48. const numerics = ProjectTokenGenerator._randomString(
  49. 10,
  50. ProjectTokenGenerator.TOKEN_NUMERICS
  51. )
  52. const token = ProjectTokenGenerator._randomString(
  53. 12,
  54. ProjectTokenGenerator.TOKEN_ALPHA
  55. )
  56. const fullToken = `${numerics}${token}`
  57. return { token: fullToken, numericPrefix: numerics }
  58. },
  59. generateUniqueReadOnlyToken(callback) {
  60. if (callback == null) {
  61. callback = function(err, token) {}
  62. }
  63. return Async.retry(
  64. 10,
  65. function(cb) {
  66. const token = ProjectTokenGenerator.readOnlyToken()
  67. if (!Features.hasFeature('overleaf-integration')) {
  68. return cb(null, token)
  69. }
  70. return V1Api.request(
  71. {
  72. url: `/api/v1/sharelatex/docs/read_token/${token}/exists`,
  73. json: true
  74. },
  75. function(err, response, body) {
  76. if (err != null) {
  77. return cb(err)
  78. }
  79. if (response.statusCode !== 200) {
  80. return cb(
  81. new Error(
  82. `non-200 response from v1 read-token-exists api: ${response.statusCode}`
  83. )
  84. )
  85. }
  86. if (body.exists === true) {
  87. return cb(new Error(`token already exists in v1: ${token}`))
  88. } else {
  89. return cb(null, token)
  90. }
  91. }
  92. )
  93. },
  94. callback
  95. )
  96. }
  97. }
  98. ProjectTokenGenerator.promises = {
  99. generateUniqueReadOnlyToken: promisify(
  100. ProjectTokenGenerator.generateUniqueReadOnlyToken
  101. )
  102. }
  103. module.exports = ProjectTokenGenerator