ProjectTokenGenerator.coffee 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. crypto = require 'crypto'
  2. V1Api = require('../V1/V1Api')
  3. Async = require('async')
  4. logger = require('logger-sharelatex')
  5. # This module mirrors the token generation in Overleaf (`random_token.rb`),
  6. # for the purposes of implementing token-based project access, like the
  7. # 'unlisted-projects' feature in Overleaf
  8. module.exports = ProjectTokenGenerator =
  9. # (From Overleaf `random_token.rb`)
  10. # Letters (not numbers! see generate_token) used in tokens. They're all
  11. # consonants, to avoid embarassing words (I can't think of any that use only
  12. # a y), and lower case "l" is omitted, because in many fonts it is
  13. # indistinguishable from an upper case "I" (and sometimes even the number 1).
  14. TOKEN_ALPHA: 'bcdfghjkmnpqrstvwxyz'
  15. TOKEN_NUMERICS: '123456789'
  16. _randomString: (length, alphabet) ->
  17. result = crypto.randomBytes(length).toJSON().data.map(
  18. (b) -> alphabet[b % alphabet.length]
  19. ).join('')
  20. return result
  21. # Generate a 12-char token with only characters from TOKEN_ALPHA,
  22. # suitable for use as a read-only token for a project
  23. readOnlyToken: () ->
  24. return ProjectTokenGenerator._randomString(
  25. 12,
  26. ProjectTokenGenerator.TOKEN_ALPHA
  27. )
  28. # Generate a longer token, with a numeric prefix,
  29. # suitable for use as a read-and-write token for a project
  30. readAndWriteToken: () ->
  31. numerics = ProjectTokenGenerator._randomString(
  32. 10,
  33. ProjectTokenGenerator.TOKEN_NUMERICS
  34. )
  35. token = ProjectTokenGenerator._randomString(
  36. 12,
  37. ProjectTokenGenerator.TOKEN_ALPHA
  38. )
  39. fullToken = "#{numerics}#{token}"
  40. return fullToken
  41. generateUniqueReadOnlyToken: (callback=(err, token)->) ->
  42. Async.retry 10
  43. , (cb) ->
  44. token = ProjectTokenGenerator.readOnlyToken()
  45. logger.log {token}, "Generated read-only token"
  46. V1Api.request {
  47. url: "/api/v1/sharelatex/docs/read_token/#{token}/exists",
  48. json: true
  49. }, (err, response, body) ->
  50. return cb(err) if err?
  51. if response.statusCode != 200
  52. return cb(new Error("non-200 response from v1 read-token-exists api: #{response.statusCode}"))
  53. if body.exists == true
  54. cb(new Error("token already exists in v1: #{token}"))
  55. else
  56. logger.log {token}, "Read-only token does not exist in v1, good to use"
  57. cb(null, token)
  58. , callback