Merge pull request #3727 from overleaf/ab-ta-unique-referal-id

Generate User referal_id using longer and more complex token to avoid duplicates

GitOrigin-RevId: 302515b0250fec875dcb7b3a505c1c7be4189e2b
This commit is contained in:
Miguel Serrano 2021-03-31 11:28:19 +02:00 committed by Copybot
parent 7a36373a0f
commit 65d9186e0b
4 changed files with 35 additions and 39 deletions

View file

@ -6,7 +6,7 @@ const logger = require('logger-sharelatex')
const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
const PublicAccessLevels = require('../Authorization/PublicAccessLevels')
const Errors = require('../Errors/Errors')
const ProjectTokenGenerator = require('./ProjectTokenGenerator')
const TokenGenerator = require('../TokenGenerator/TokenGenerator')
const ProjectHelper = require('./ProjectHelper')
const settings = require('settings-sharelatex')
const { callbackify } = require('util')
@ -236,11 +236,11 @@ async function _generateTokens(project, callback) {
}
const { tokens } = project
if (tokens.readAndWrite == null) {
const { token, numericPrefix } = ProjectTokenGenerator.readAndWriteToken()
const { token, numericPrefix } = TokenGenerator.readAndWriteToken()
tokens.readAndWrite = token
tokens.readAndWritePrefix = numericPrefix
}
if (tokens.readOnly == null) {
tokens.readOnly = await ProjectTokenGenerator.promises.generateUniqueReadOnlyToken()
tokens.readOnly = await TokenGenerator.promises.generateUniqueReadOnlyToken()
}
}

View file

@ -16,19 +16,21 @@ const Features = require('../../infrastructure/Features')
const Async = require('async')
const { promisify } = require('util')
// (From Overleaf `random_token.rb`)
// Letters (not numbers! see generate_token) used in tokens. They're all
// consonants, to avoid embarassing words (I can't think of any that use only
// a y), and lower case "l" is omitted, because in many fonts it is
// indistinguishable from an upper case "I" (and sometimes even the number 1).
const TOKEN_LOWERCASE_ALPHA = 'bcdfghjkmnpqrstvwxyz'
const TOKEN_NUMERICS = '123456789'
const TOKEN_ALPHANUMERICS =
TOKEN_LOWERCASE_ALPHA + TOKEN_LOWERCASE_ALPHA.toUpperCase() + TOKEN_NUMERICS
// This module mirrors the token generation in Overleaf (`random_token.rb`),
// for the purposes of implementing token-based project access, like the
// 'unlisted-projects' feature in Overleaf
const ProjectTokenGenerator = {
// (From Overleaf `random_token.rb`)
// Letters (not numbers! see generate_token) used in tokens. They're all
// consonants, to avoid embarassing words (I can't think of any that use only
// a y), and lower case "l" is omitted, because in many fonts it is
// indistinguishable from an upper case "I" (and sometimes even the number 1).
TOKEN_ALPHA: 'bcdfghjkmnpqrstvwxyz',
TOKEN_NUMERICS: '123456789',
const TokenGenerator = {
_randomString(length, alphabet) {
const result = crypto
.randomBytes(length)
@ -38,30 +40,25 @@ const ProjectTokenGenerator = {
return result
},
// Generate a 12-char token with only characters from TOKEN_ALPHA,
// Generate a 12-char token with only characters from TOKEN_LOWERCASE_ALPHA,
// suitable for use as a read-only token for a project
readOnlyToken() {
return ProjectTokenGenerator._randomString(
12,
ProjectTokenGenerator.TOKEN_ALPHA
)
return TokenGenerator._randomString(12, TOKEN_LOWERCASE_ALPHA)
},
// Generate a longer token, with a numeric prefix,
// suitable for use as a read-and-write token for a project
readAndWriteToken() {
const numerics = ProjectTokenGenerator._randomString(
10,
ProjectTokenGenerator.TOKEN_NUMERICS
)
const token = ProjectTokenGenerator._randomString(
12,
ProjectTokenGenerator.TOKEN_ALPHA
)
const numerics = TokenGenerator._randomString(10, TOKEN_NUMERICS)
const token = TokenGenerator._randomString(12, TOKEN_LOWERCASE_ALPHA)
const fullToken = `${numerics}${token}`
return { token: fullToken, numericPrefix: numerics }
},
generateReferralId() {
return TokenGenerator._randomString(16, TOKEN_ALPHANUMERICS)
},
generateUniqueReadOnlyToken(callback) {
if (callback == null) {
callback = function(err, token) {}
@ -69,7 +66,7 @@ const ProjectTokenGenerator = {
return Async.retry(
10,
function(cb) {
const token = ProjectTokenGenerator.readOnlyToken()
const token = TokenGenerator.readOnlyToken()
if (!Features.hasFeature('overleaf-integration')) {
return cb(null, token)
@ -104,9 +101,9 @@ const ProjectTokenGenerator = {
}
}
ProjectTokenGenerator.promises = {
TokenGenerator.promises = {
generateUniqueReadOnlyToken: promisify(
ProjectTokenGenerator.generateUniqueReadOnlyToken
TokenGenerator.generateUniqueReadOnlyToken
)
}
module.exports = ProjectTokenGenerator
module.exports = TokenGenerator

View file

@ -1,6 +1,6 @@
const Settings = require('settings-sharelatex')
const mongoose = require('../infrastructure/Mongoose')
const uuid = require('uuid')
const TokenGenerator = require('../Features/TokenGenerator/TokenGenerator')
const { Schema } = mongoose
const { ObjectId } = Schema
@ -136,7 +136,7 @@ const UserSchema = new Schema({
referal_id: {
type: String,
default() {
return uuid.v4().split('-')[0]
return TokenGenerator.generateReferralId()
}
},
refered_users: [{ type: ObjectId, ref: 'User' }],

View file

@ -56,7 +56,7 @@ describe('ProjectDetailsHandler', function() {
moveEntity: sinon.stub().resolves()
}
}
this.ProjectTokenGenerator = {
this.TokenGenerator = {
readAndWriteToken: sinon.stub(),
promises: {
generateUniqueReadOnlyToken: sinon.stub()
@ -82,7 +82,7 @@ describe('ProjectDetailsHandler', function() {
warn() {},
err() {}
},
'./ProjectTokenGenerator': this.ProjectTokenGenerator,
'../TokenGenerator/TokenGenerator': this.TokenGenerator,
'settings-sharelatex': this.settings
}
})
@ -484,10 +484,10 @@ describe('ProjectDetailsHandler', function() {
this.readOnlyToken = 'abc'
this.readAndWriteToken = '42def'
this.readAndWriteTokenPrefix = '42'
this.ProjectTokenGenerator.promises.generateUniqueReadOnlyToken.resolves(
this.TokenGenerator.promises.generateUniqueReadOnlyToken.resolves(
this.readOnlyToken
)
this.ProjectTokenGenerator.readAndWriteToken.returns({
this.TokenGenerator.readAndWriteToken.returns({
token: this.readAndWriteToken,
numericPrefix: this.readAndWriteTokenPrefix
})
@ -506,10 +506,9 @@ describe('ProjectDetailsHandler', function() {
it('should update the project with new tokens', async function() {
await this.handler.promises.ensureTokensArePresent(this.project._id)
expect(this.ProjectTokenGenerator.promises.generateUniqueReadOnlyToken)
.to.have.been.calledOnce
expect(this.ProjectTokenGenerator.readAndWriteToken).to.have.been
.calledOnce
expect(this.TokenGenerator.promises.generateUniqueReadOnlyToken).to.have
.been.calledOnce
expect(this.TokenGenerator.readAndWriteToken).to.have.been.calledOnce
expect(this.ProjectModel.updateOne).to.have.been.calledOnce
expect(this.ProjectModel.updateOne).to.have.been.calledWith(
{ _id: this.project._id },