7c70b749d4
* [monorepo] remove PII and variables from error messages Exclusions: - scripts - tests - fuzzing - SplitTestManager (messages are sent to admin frontend) - Group setup (we may want an error per unique tuple) - sharejs (unused types; text type errors are shadowed already) - history-v1 error messages that are used by the ErrorRecorder - errors that flag issues with configuration/call signatures I've used these search terms for finding unwanted error messages: - new Error(` - new Error\(\n\s+` (regex search) - new OError(` - new OError\(\n\s+` (regex search) * [web] throw NotFoundError from ProjectLocator * [github-sync] fix OError.tag call in script Co-authored-by: Jessica Lawshe <jessica.lawshe@overleaf.com> * [templates] revert changes to test client --------- Co-authored-by: Jessica Lawshe <jessica.lawshe@overleaf.com> GitOrigin-RevId: 736857a4fc5d9bfb0f8cb03e0f004eda87e5a220
99 lines
2.8 KiB
JavaScript
99 lines
2.8 KiB
JavaScript
import crypto from 'node:crypto'
|
|
import V1Api from '../V1/V1Api.mjs'
|
|
import Features from '../../infrastructure/Features.mjs'
|
|
import { callbackify } from 'node:util'
|
|
import OError from '@overleaf/o-error'
|
|
|
|
// (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
|
|
|
|
function _randomString(length, alphabet) {
|
|
const result = crypto
|
|
.randomBytes(length)
|
|
.toJSON()
|
|
.data.map(b => alphabet[b % alphabet.length])
|
|
.join('')
|
|
return result
|
|
}
|
|
|
|
// Generate a 12-char token with only characters from TOKEN_LOWERCASE_ALPHA,
|
|
// suitable for use as a read-only token for a project
|
|
function readOnlyToken() {
|
|
return _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
|
|
function readAndWriteToken() {
|
|
const numerics = _randomString(10, TOKEN_NUMERICS)
|
|
const token = _randomString(12, TOKEN_LOWERCASE_ALPHA)
|
|
const fullToken = `${numerics}${token}`
|
|
return { token: fullToken, numericPrefix: numerics }
|
|
}
|
|
|
|
function generateReferralId() {
|
|
return _randomString(16, TOKEN_ALPHANUMERICS)
|
|
}
|
|
|
|
async function generateUniqueReadOnlyToken() {
|
|
const retryOperation = async () => {
|
|
const token = readOnlyToken()
|
|
|
|
if (!Features.hasFeature('saas')) {
|
|
return token
|
|
}
|
|
|
|
const { response, body } = await V1Api.promises.request({
|
|
url: `/api/v1/overleaf/docs/read_token/${token}/exists`,
|
|
json: true,
|
|
})
|
|
|
|
if (response.statusCode !== 200) {
|
|
throw new OError('non-200 response from v1 read-token-exists api', {
|
|
statusCode: response.statusCode,
|
|
})
|
|
}
|
|
|
|
if (body.exists === true) {
|
|
throw new OError('token already exists in v1', { token })
|
|
}
|
|
|
|
return token
|
|
}
|
|
|
|
const MAX_RETRIES = 10
|
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
try {
|
|
return await retryOperation()
|
|
} catch (err) {
|
|
if (attempt === MAX_RETRIES) {
|
|
throw err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const TokenGenerator = {
|
|
_randomString,
|
|
readOnlyToken,
|
|
readAndWriteToken,
|
|
generateReferralId,
|
|
generateUniqueReadOnlyToken: callbackify(generateUniqueReadOnlyToken),
|
|
}
|
|
|
|
TokenGenerator.promises = {
|
|
generateUniqueReadOnlyToken,
|
|
}
|
|
export default TokenGenerator
|