Spaces:
Running
Running
import fs from 'node:fs'; | |
import path from 'node:path'; | |
import express from 'express'; | |
import { sync as writeFileAtomicSync } from 'write-file-atomic'; | |
import { getConfigValue } from '../util.js'; | |
export const SECRETS_FILE = 'secrets.json'; | |
export const SECRET_KEYS = { | |
HORDE: 'api_key_horde', | |
MANCER: 'api_key_mancer', | |
VLLM: 'api_key_vllm', | |
APHRODITE: 'api_key_aphrodite', | |
TABBY: 'api_key_tabby', | |
OPENAI: 'api_key_openai', | |
NOVEL: 'api_key_novel', | |
CLAUDE: 'api_key_claude', | |
DEEPL: 'deepl', | |
LIBRE: 'libre', | |
LIBRE_URL: 'libre_url', | |
LINGVA_URL: 'lingva_url', | |
OPENROUTER: 'api_key_openrouter', | |
SCALE: 'api_key_scale', | |
AI21: 'api_key_ai21', | |
SCALE_COOKIE: 'scale_cookie', | |
ONERING_URL: 'oneringtranslator_url', | |
DEEPLX_URL: 'deeplx_url', | |
MAKERSUITE: 'api_key_makersuite', | |
SERPAPI: 'api_key_serpapi', | |
TOGETHERAI: 'api_key_togetherai', | |
MISTRALAI: 'api_key_mistralai', | |
CUSTOM: 'api_key_custom', | |
OOBA: 'api_key_ooba', | |
INFERMATICAI: 'api_key_infermaticai', | |
DREAMGEN: 'api_key_dreamgen', | |
NOMICAI: 'api_key_nomicai', | |
KOBOLDCPP: 'api_key_koboldcpp', | |
LLAMACPP: 'api_key_llamacpp', | |
COHERE: 'api_key_cohere', | |
PERPLEXITY: 'api_key_perplexity', | |
GROQ: 'api_key_groq', | |
AZURE_TTS: 'api_key_azure_tts', | |
FEATHERLESS: 'api_key_featherless', | |
ZEROONEAI: 'api_key_01ai', | |
HUGGINGFACE: 'api_key_huggingface', | |
STABILITY: 'api_key_stability', | |
BLOCKENTROPY: 'api_key_blockentropy', | |
CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts', | |
TAVILY: 'api_key_tavily', | |
NANOGPT: 'api_key_nanogpt', | |
BFL: 'api_key_bfl', | |
FALAI: 'api_key_falai', | |
GENERIC: 'api_key_generic', | |
DEEPSEEK: 'api_key_deepseek', | |
SERPER: 'api_key_serper', | |
}; | |
// These are the keys that are safe to expose, even if allowKeysExposure is false | |
const EXPORTABLE_KEYS = [ | |
SECRET_KEYS.LIBRE_URL, | |
SECRET_KEYS.LINGVA_URL, | |
SECRET_KEYS.ONERING_URL, | |
SECRET_KEYS.DEEPLX_URL, | |
]; | |
/** | |
* Writes a secret to the secrets file | |
* @param {import('../users.js').UserDirectoryList} directories User directories | |
* @param {string} key Secret key | |
* @param {string} value Secret value | |
*/ | |
export function writeSecret(directories, key, value) { | |
const filePath = path.join(directories.root, SECRETS_FILE); | |
if (!fs.existsSync(filePath)) { | |
const emptyFile = JSON.stringify({}); | |
writeFileAtomicSync(filePath, emptyFile, 'utf-8'); | |
} | |
const fileContents = fs.readFileSync(filePath, 'utf-8'); | |
const secrets = JSON.parse(fileContents); | |
secrets[key] = value; | |
writeFileAtomicSync(filePath, JSON.stringify(secrets, null, 4), 'utf-8'); | |
} | |
/** | |
* Deletes a secret from the secrets file | |
* @param {import('../users.js').UserDirectoryList} directories User directories | |
* @param {string} key Secret key | |
* @returns | |
*/ | |
export function deleteSecret(directories, key) { | |
const filePath = path.join(directories.root, SECRETS_FILE); | |
if (!fs.existsSync(filePath)) { | |
return; | |
} | |
const fileContents = fs.readFileSync(filePath, 'utf-8'); | |
const secrets = JSON.parse(fileContents); | |
delete secrets[key]; | |
writeFileAtomicSync(filePath, JSON.stringify(secrets, null, 4), 'utf-8'); | |
} | |
/** | |
* Reads a secret from the secrets file | |
* @param {import('../users.js').UserDirectoryList} directories User directories | |
* @param {string} key Secret key | |
* @returns {string} Secret value | |
*/ | |
export function readSecret(directories, key) { | |
const filePath = path.join(directories.root, SECRETS_FILE); | |
if (!fs.existsSync(filePath)) { | |
return ''; | |
} | |
const fileContents = fs.readFileSync(filePath, 'utf-8'); | |
const secrets = JSON.parse(fileContents); | |
return secrets[key]; | |
} | |
/** | |
* Reads the secret state from the secrets file | |
* @param {import('../users.js').UserDirectoryList} directories User directories | |
* @returns {object} Secret state | |
*/ | |
export function readSecretState(directories) { | |
const filePath = path.join(directories.root, SECRETS_FILE); | |
if (!fs.existsSync(filePath)) { | |
return {}; | |
} | |
const fileContents = fs.readFileSync(filePath, 'utf8'); | |
const secrets = JSON.parse(fileContents); | |
const state = {}; | |
for (const key of Object.values(SECRET_KEYS)) { | |
state[key] = !!secrets[key]; // convert to boolean | |
} | |
return state; | |
} | |
/** | |
* Reads all secrets from the secrets file | |
* @param {import('../users.js').UserDirectoryList} directories User directories | |
* @returns {Record<string, string> | undefined} Secrets | |
*/ | |
export function getAllSecrets(directories) { | |
const filePath = path.join(directories.root, SECRETS_FILE); | |
if (!fs.existsSync(filePath)) { | |
console.error('Secrets file does not exist'); | |
return undefined; | |
} | |
const fileContents = fs.readFileSync(filePath, 'utf8'); | |
const secrets = JSON.parse(fileContents); | |
return secrets; | |
} | |
export const router = express.Router(); | |
router.post('/write', (request, response) => { | |
const key = request.body.key; | |
const value = request.body.value; | |
writeSecret(request.user.directories, key, value); | |
return response.send('ok'); | |
}); | |
router.post('/read', (request, response) => { | |
try { | |
const state = readSecretState(request.user.directories); | |
return response.send(state); | |
} catch (error) { | |
console.error(error); | |
return response.send({}); | |
} | |
}); | |
router.post('/view', async (request, response) => { | |
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean'); | |
if (!allowKeysExposure) { | |
console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.yaml is set to true'); | |
return response.sendStatus(403); | |
} | |
try { | |
const secrets = getAllSecrets(request.user.directories); | |
if (!secrets) { | |
return response.sendStatus(404); | |
} | |
return response.send(secrets); | |
} catch (error) { | |
console.error(error); | |
return response.sendStatus(500); | |
} | |
}); | |
router.post('/find', (request, response) => { | |
const allowKeysExposure = getConfigValue('allowKeysExposure', false, 'boolean'); | |
const key = request.body.key; | |
if (!allowKeysExposure && !EXPORTABLE_KEYS.includes(key)) { | |
console.error('Cannot fetch secrets unless allowKeysExposure in config.yaml is set to true'); | |
return response.sendStatus(403); | |
} | |
try { | |
const secret = readSecret(request.user.directories, key); | |
if (!secret) { | |
return response.sendStatus(404); | |
} | |
return response.send({ value: secret }); | |
} catch (error) { | |
console.error(error); | |
return response.sendStatus(500); | |
} | |
}); | |