mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user