Github Sync: enhance token handling and post-sync indexing

- support file-based token cipher
  - reindex references after sync
  - fix typo and clean up code
This commit is contained in:
yu-i-i
2026-09-22 02:41:43 +02:00
parent 9afab5ade1
commit bd5507262e
5 changed files with 102 additions and 23 deletions
@@ -0,0 +1,82 @@
import logger from '@overleaf/logger'
import Settings from '@overleaf/settings'
import fs from 'node:fs'
import crypto from 'node:crypto'
import Path from 'node:path'
import AccessTokenEncryptorClass from '@overleaf/access-token-encryptor'
const TOKEN_CIPHER_FILE = '/var/lib/overleaf/data/.token-cipher.json'
const TOKEN_CIPHER_LABEL = 'OL_CEP-v3'
let encryptorInstance = null
/**
* Get or create a stable encryptor data that persists across container
* recreations. Priority:
* 1. GITHUB_CIPHER_PASSWORD || TOKEN_CIPHER_PASSWORD env var (explicit user config)
* 2. JSON file with encryptor data in the persistent volume (/var/lib/overleaf/data/)
* — auto-generated on first use, survives container rebuilds
*/
function _getEncryptorData() {
const cipherPassword = process.env.GITHUB_TOKEN_CIPHER_PASSWORD || process.env.TOKEN_CIPHER_PASSWORD
const cipherLabel = process.env.GITHUB_TOKEN_CIPHER_LABEL ||
process.env.TOKEN_CIPHER_LABEL ||
TOKEN_CIPHER_LABEL
if (cipherPassword) {
return {
cipherLabel,
cipherPasswords: {
[cipherLabel]: cipherPassword
}
}
}
// cipherPassword is not set, use file
const cipherFile = process.env.GITHUB_TOKEN_CIPHER_FILE ||
process.env.TOKEN_CIPHER_FILE ||
TOKEN_CIPHER_FILE
try {
return JSON.parse(fs.readFileSync(cipherFile, 'utf8'))
} catch (err) {
if (err.code === 'ENOENT') {
logger.info({ cipherFile }, 'Creating new cipherFile')
} else {
logger.error({ err, cipherFile }, 'Bad cipherFile')
throw err
}
}
// File doesn't exist yet — generate one
try {
const encryptorData = {
cipherLabel,
cipherPasswords: {
[cipherLabel]: crypto.randomBytes(32).toString('base64')
},
}
const dir = Path.dirname(cipherFile)
fs.mkdirSync(dir, { recursive: true })
fs.writeFileSync(cipherFile, JSON.stringify(encryptorData, null, 2), { mode: 0o600 })
return encryptorData
} catch (err) {
logger.error({ err, cipherFile }, 'Cannot create cipherFile')
throw err
}
}
function _getEncryptor() {
if (!encryptorInstance) {
const encryptorData = _getEncryptorData()
encryptorInstance = new AccessTokenEncryptorClass(encryptorData)
}
return encryptorInstance
}
export const AccessTokenEncryptor = {
async encryptJson(json) {
return await _getEncryptor().promises.encryptJson(json)
},
async decryptToJson(encryptedJson) {
return await _getEncryptor().promises.decryptToJson(encryptedJson)
},
}
@@ -311,7 +311,7 @@ async function resolveConflictSyncState(
}
}
// ----------- detached flow
// ----------- detached flow
// GH repo was force pushed, lastSybc commit possibly anavailable
async function resolveDetachedSyncState(
token,
@@ -574,7 +574,7 @@ async function buildDetachedSyncPlan({
buffer = await HistoryManager.getProjectFileBuffer(projectId, currentVersion, path)
}
const sha = await api.uploadBlob(token, repoFullName, buffer)
// assert sha !== localHash
// assert sha !== localHash
}
// OL == GH, do nothing
@@ -1,23 +1,15 @@
import logger from '@overleaf/logger'
import OError from '@overleaf/o-error'
import AccessTokenEncryptor from '@overleaf/access-token-encryptor'
import Mongo from '../../../../app/src/Features/Helpers/Mongo.mjs'
import { GitHubSyncUserCredentials } from '../models/githubSyncUserCredentials.mjs'
import { AccessTokenEncryptor } from './AccessTokenEncryptorHelper.mjs'
import { InvalidTokenError } from './GitSyncErrors.mjs'
const { normalizeQuery } = Mongo
// encrypt / decrypt helpers
const accessTokenEncryptor = new AccessTokenEncryptor({
cipherLabel: process.env.GITHUB_TOKEN_CIPHER_LABEL || "OL_CEP-v3",
cipherPasswords: {
[process.env.GITHUB_TOKEN_CIPHER_LABEL || "OL_CEP-v3"]: process.env.GITHUB_TOKEN_CIPHER_PASSWORD,
},
})
async function encryptAccessToken(accessToken) {
try {
return await accessTokenEncryptor.promises.encryptJson(accessToken)
return await AccessTokenEncryptor.encryptJson(accessToken)
} catch (err) {
throw OError.tag('failed to encrypt token', err)
}
@@ -25,7 +17,7 @@ async function encryptAccessToken(accessToken) {
async function decryptAccessToken(tokenEncrypted) {
try {
return await accessTokenEncryptor.promises.decryptToJson(tokenEncrypted)
return await AccessTokenEncryptor.decryptToJson(tokenEncrypted)
} catch (err) {
throw new InvalidTokenError('failed to decrypt token', { status: 401 }, err)
}