fix: conditionally include secrets in user data backup (#5360)
* fix: conditionally include secrets in user data backup * feat: add full data backup toggle * 418 -> 403 I'm not a teapot * Distinguish fails from disabled
This commit is contained in:
@@ -165,6 +165,8 @@ rateLimiting:
|
||||
|
||||
## BACKUP CONFIGURATION
|
||||
backups:
|
||||
# Allow users to create a full backup archive of their data
|
||||
allowFullDataBackup: true
|
||||
# Common settings for all backup types
|
||||
common:
|
||||
# Number of backups to keep for each chat and settings file
|
||||
|
||||
Vendored
+1
-1
@@ -40,7 +40,7 @@ declare global {
|
||||
/**
|
||||
* Authenticated user handle.
|
||||
*/
|
||||
handle: string;
|
||||
handle: string | null;
|
||||
/**
|
||||
* Last time the session was extended.
|
||||
*/
|
||||
|
||||
@@ -277,6 +277,29 @@ function getActiveSecretLabel(key) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if secrets can be viewed based on server configuration.
|
||||
* @returns {Promise<boolean|null>} A boolean value, or null if the request fails.
|
||||
*/
|
||||
export async function canViewSecrets() {
|
||||
try {
|
||||
const response = await fetch('/api/secrets/settings', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders({ omitContentType: true }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data?.allowKeysExposure === true;
|
||||
} catch (error) {
|
||||
console.error('Error getting secrets settings:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function viewSecrets() {
|
||||
const response = await fetch('/api/secrets/view', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getRequestHeaders } from '../script.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||
import { canViewSecrets } from './secrets.js';
|
||||
import { renderTemplateAsync } from './templates.js';
|
||||
import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './utils.js';
|
||||
|
||||
@@ -266,6 +267,11 @@ async function backupUserData(handle, callback) {
|
||||
throw new Error('Failed to backup user data');
|
||||
}
|
||||
|
||||
const includesSecrets = await canViewSecrets();
|
||||
if (includesSecrets === false) {
|
||||
toastr.warning('The backup will not include secrets due to a server configuration.', 'Secrets Not Included');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const header = response.headers.get('Content-Disposition');
|
||||
const parts = header.split(';');
|
||||
|
||||
@@ -104,7 +104,7 @@ const EXPORTABLE_KEYS = [
|
||||
SECRET_KEYS.DEEPLX_URL,
|
||||
];
|
||||
|
||||
const allowKeysExposure = !!getConfigValue('allowKeysExposure', false, 'boolean');
|
||||
export const allowKeysExposure = !!getConfigValue('allowKeysExposure', false, 'boolean');
|
||||
|
||||
/**
|
||||
* SecretManager class to handle all secret operations
|
||||
@@ -636,3 +636,7 @@ router.post('/rename', (request, response) => {
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/settings', async (_request, response) => {
|
||||
return response.send({ allowKeysExposure });
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import express from 'express';
|
||||
import { getUserAvatar, toKey, getPasswordHash, getPasswordSalt, createBackupArchive, ensurePublicDirectoriesExist, toAvatarKey } from '../users.js';
|
||||
import { SETTINGS_FILE } from '../constants.js';
|
||||
import { checkForNewContent, CONTENT_TYPES } from './content-manager.js';
|
||||
import { color, Cache } from '../util.js';
|
||||
import { color, Cache, getConfigValue } from '../util.js';
|
||||
|
||||
const RESET_CACHE = new Cache(5 * 60 * 1000);
|
||||
|
||||
@@ -138,6 +138,13 @@ router.post('/change-password', async (request, response) => {
|
||||
|
||||
router.post('/backup', async (request, response) => {
|
||||
try {
|
||||
const allowFullDataBackup = !!getConfigValue('backups.allowFullDataBackup', true, 'boolean');
|
||||
|
||||
if (!allowFullDataBackup) {
|
||||
console.warn('Backup failed: Full data backup is disabled in configuration');
|
||||
return response.status(403).json({ error: 'Full data backup is disabled' });
|
||||
}
|
||||
|
||||
const handle = request.body.handle;
|
||||
|
||||
if (!handle) {
|
||||
|
||||
+9
-2
@@ -17,7 +17,7 @@ import sanitize from 'sanitize-filename';
|
||||
|
||||
import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js';
|
||||
import { getConfigValue, color, delay, generateTimestamp, invalidateFirefoxCache, isPathUnderParent } from './util.js';
|
||||
import { readSecret, writeSecret } from './endpoints/secrets.js';
|
||||
import { allowKeysExposure, readSecret, writeSecret, SECRETS_FILE } from './endpoints/secrets.js';
|
||||
import { getContentOfType } from './endpoints/content-manager.js';
|
||||
import { serverDirectory } from './server-directory.js';
|
||||
|
||||
@@ -1052,7 +1052,14 @@ export async function createBackupArchive(handle, response) {
|
||||
archive.pipe(response);
|
||||
|
||||
// Append files from a sub-directory, putting its contents at the root of archive
|
||||
archive.directory(directories.root, false);
|
||||
const ignore = allowKeysExposure ? [] : [SECRETS_FILE];
|
||||
archive.glob('**/*', {
|
||||
cwd: directories.root,
|
||||
follow: false,
|
||||
stat: true,
|
||||
dot: true,
|
||||
ignore,
|
||||
});
|
||||
archive.finalize();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user