Compare commits

...

1 Commits

Author SHA1 Message Date
orangecoding
cabef973a2 forbid backuo/restore in demo mode 2026-05-12 12:42:25 +02:00
3 changed files with 36 additions and 4 deletions

View File

@@ -9,6 +9,10 @@ import {
precheckRestore, precheckRestore,
restoreFromZip, restoreFromZip,
} from '../../services/storage/backupRestoreService.js'; } from '../../services/storage/backupRestoreService.js';
import { getSettings } from '../../services/storage/settingsStorage.js';
import { isAdmin } from '../security.js';
const DEMO_MODE_ERROR = 'Backup and restore are not available in demo mode.';
/** /**
* @param {import('fastify').FastifyInstance} fastify * @param {import('fastify').FastifyInstance} fastify
@@ -21,7 +25,11 @@ export default async function backupPlugin(fastify) {
(req, body, done) => done(null, body), (req, body, done) => done(null, body),
); );
fastify.get('/', async (_request, reply) => { fastify.get('/', async (request, reply) => {
const settings = await getSettings();
if (settings.demoMode && !isAdmin(request)) {
return reply.code(403).send({ error: DEMO_MODE_ERROR });
}
const zipBuffer = await createBackupZip(); const zipBuffer = await createBackupZip();
const fileName = await buildBackupFileName(); const fileName = await buildBackupFileName();
reply.header('Content-Type', 'application/zip'); reply.header('Content-Type', 'application/zip');
@@ -30,6 +38,10 @@ export default async function backupPlugin(fastify) {
}); });
fastify.post('/restore', async (request, reply) => { fastify.post('/restore', async (request, reply) => {
const settings = await getSettings();
if (settings.demoMode && !isAdmin(request)) {
return reply.code(403).send({ error: DEMO_MODE_ERROR });
}
const { dryRun = 'false', force = 'false' } = request.query || {}; const { dryRun = 'false', force = 'false' } = request.query || {};
const doDryRun = String(dryRun) === 'true'; const doDryRun = String(dryRun) === 'true';
const doForce = String(force) === 'true'; const doForce = String(force) === 'true';

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "22.0.3", "version": "22.0.4",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"prepare": "husky", "prepare": "husky",

View File

@@ -54,6 +54,7 @@ const GeneralSettings = function GeneralSettings() {
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const settings = useSelector((state) => state.generalSettings.settings); const settings = useSelector((state) => state.generalSettings.settings);
const currentUser = useSelector((state) => state.user.currentUser);
const [interval, setInterval] = React.useState(''); const [interval, setInterval] = React.useState('');
const [port, setPort] = React.useState(''); const [port, setPort] = React.useState('');
@@ -467,12 +468,26 @@ const GeneralSettings = function GeneralSettings() {
itemKey="backup" itemKey="backup"
> >
<div className="generalSettings__tab-content"> <div className="generalSettings__tab-content">
{demoMode && !currentUser?.isAdmin && (
<Banner
fullMode={false}
type="warning"
closeIcon={null}
style={{ marginBottom: '12px' }}
description="Backup and restore are not available in demo mode."
/>
)}
<SegmentPart <SegmentPart
name="Backup & Restore" name="Backup & Restore"
helpText="Download a zipped backup of your database or restore from a backup zip." helpText="Download a zipped backup of your database or restore from a backup zip."
> >
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', flexWrap: 'wrap' }}>
<Button theme="solid" icon={<IconSave />} onClick={handleDownloadBackup}> <Button
theme="solid"
icon={<IconSave />}
onClick={handleDownloadBackup}
disabled={demoMode && !currentUser?.isAdmin}
>
Download Backup Download Backup
</Button> </Button>
<input <input
@@ -482,7 +497,12 @@ const GeneralSettings = function GeneralSettings() {
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleSelectRestoreFile} onChange={handleSelectRestoreFile}
/> />
<Button onClick={handleOpenFilePicker} theme="light" icon={<IconFolder />}> <Button
onClick={handleOpenFilePicker}
theme="light"
icon={<IconFolder />}
disabled={demoMode && !currentUser?.isAdmin}
>
Restore from Zip Restore from Zip
</Button> </Button>
</div> </div>