added security settings

changed fixes and issues
This commit is contained in:
Philipp
2026-06-04 15:08:54 +02:00
parent 28a453d87f
commit 8a80978aef
5 changed files with 169 additions and 12 deletions
+16 -6
View File
@@ -83,10 +83,15 @@ export function resticProcessEnv() {
export async function readEnvSettings() {
const fileValues = await readEnvFile();
return editableEnv.map((field) => ({
...field,
value: fileValues[field.key] ?? process.env[field.key] ?? '',
}));
return editableEnv.map((field) => {
const value = fileValues[field.key] ?? process.env[field.key] ?? '';
// Secrets are write-only: never echo their value back to callers. The UI
// only learns whether a value is currently set via `hasValue`.
if (field.secret) {
return { ...field, value: '', hasValue: Boolean(value) };
}
return { ...field, value };
});
}
export async function writeEnvSettings(values) {
@@ -94,14 +99,19 @@ export async function writeEnvSettings(values) {
const currentValues = await readEnvFile();
const nextValues = { ...currentValues };
const secretKeys = new Set(editableEnv.filter((field) => field.secret).map((field) => field.key));
for (const [key, value] of Object.entries(values || {})) {
if (!allowedKeys.has(key)) continue;
if (key === 'API_TOKEN' && String(value || '').length < minApiTokenLength) {
const text = String(value ?? '');
// Secrets are write-only: an empty submission keeps the existing value so
// the masked UI does not wipe credentials when saving unrelated fields.
if (secretKeys.has(key) && text === '') continue;
if (key === 'API_TOKEN' && text.length < minApiTokenLength) {
const error = new Error(`API_TOKEN must be at least ${minApiTokenLength} characters long.`);
error.status = 400;
throw error;
}
nextValues[key] = String(value ?? '');
nextValues[key] = text;
}
const body = editableEnv
+9 -1
View File
@@ -1,5 +1,6 @@
import cors from 'cors';
import express from 'express';
import crypto from 'node:crypto';
import http from 'node:http';
import https from 'node:https';
import { readFile } from 'node:fs/promises';
@@ -25,7 +26,7 @@ app.use((req, res, next) => {
return;
}
const header = req.get('authorization') || '';
if (header === `Bearer ${config.apiToken}`) {
if (config.apiToken && timingSafeEqual(header, `Bearer ${config.apiToken}`)) {
next();
return;
}
@@ -36,6 +37,13 @@ function normalizeIp(value) {
return String(value || '').replace(/^::ffff:/, '');
}
function timingSafeEqual(a, b) {
const bufA = Buffer.from(String(a));
const bufB = Buffer.from(String(b));
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
}
app.use('/api/health', healthRouter);
app.use('/api/vms', vmsRouter);
app.use('/api/snapshots', snapshotsRouter);