|
|
|
|
@@ -37,7 +37,8 @@ import {
|
|
|
|
|
} from './security.js';
|
|
|
|
|
|
|
|
|
|
function buildPathWithQuery(pathname, params = {}) {
|
|
|
|
|
const query = new URLSearchParams();
|
|
|
|
|
const [pathOnly, existingQuery = ''] = String(pathname).split('?');
|
|
|
|
|
const query = new URLSearchParams(existingQuery);
|
|
|
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(params)) {
|
|
|
|
|
if (value) {
|
|
|
|
|
@@ -46,7 +47,7 @@ function buildPathWithQuery(pathname, params = {}) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const serialized = query.toString();
|
|
|
|
|
return serialized ? `${pathname}?${serialized}` : pathname;
|
|
|
|
|
return serialized ? `${pathOnly}?${serialized}` : pathOnly;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function redirectWithError(pathname, message) {
|
|
|
|
|
@@ -57,6 +58,10 @@ function redirectWithSuccess(pathname, message) {
|
|
|
|
|
redirect(buildPathWithQuery(pathname, { success: message }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function adminFilesTabHref(relativePath, tab) {
|
|
|
|
|
return buildPathWithQuery(adminFilesHref(relativePath), { tab });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getUploadId(formData, fieldName = 'uploadId') {
|
|
|
|
|
const parsed = Number.parseInt(String(formData.get(fieldName) || ''), 10);
|
|
|
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
|
|
|
@@ -156,6 +161,33 @@ async function copyPath(sourcePath, targetPath) {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function savedUploadFileName(originalName, storedName) {
|
|
|
|
|
const fallback = safeBaseName(storedName, 'upload');
|
|
|
|
|
const candidate = safeBaseName(originalName, fallback).replace(/[\\/]/g, '_');
|
|
|
|
|
return isValidNodeName(candidate) ? candidate : fallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function resolveSavedUploadTarget(targetRelativePath, fileName) {
|
|
|
|
|
const targetPath = resolveAdminPath(targetRelativePath);
|
|
|
|
|
if (!targetPath) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const targetStat = await fs.promises.stat(targetPath);
|
|
|
|
|
if (targetStat.isDirectory()) {
|
|
|
|
|
return path.join(targetPath, fileName);
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isValidNodeName(path.basename(targetPath))) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return targetPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function userLoginAction(formData) {
|
|
|
|
|
await runCleanupIfNeeded();
|
|
|
|
|
|
|
|
|
|
@@ -175,13 +207,13 @@ export async function userLoginAction(formData) {
|
|
|
|
|
|
|
|
|
|
const username = String(formData.get('username') || '').trim();
|
|
|
|
|
const password = String(formData.get('password') || '');
|
|
|
|
|
const row = await get('SELECT password_hash FROM users WHERE username = ?', [username]);
|
|
|
|
|
const row = await get('SELECT password_hash, is_admin FROM users WHERE username = ?', [username]);
|
|
|
|
|
|
|
|
|
|
if (!row || !bcrypt.compareSync(password, row.password_hash)) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/login`, 'Anmeldung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await setAuthCookie({ sub: username });
|
|
|
|
|
await setAuthCookie({ sub: username, admin: Boolean(row.is_admin), auth: 'user' });
|
|
|
|
|
await clearLoginRateLimit('user');
|
|
|
|
|
await logEvent('login', username, { ok: true }, await getRequestMeta());
|
|
|
|
|
redirect(`${managementBasePath}/dashboard`);
|
|
|
|
|
@@ -191,7 +223,7 @@ export async function userLogoutAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=upload`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await clearAuthCookie();
|
|
|
|
|
@@ -222,7 +254,7 @@ export async function adminLoginAction(formData) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin`, 'Anmeldung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await setAuthCookie({ sub: 'admin', admin: true });
|
|
|
|
|
await setAuthCookie({ sub: 'admin', admin: true, auth: 'admin' });
|
|
|
|
|
await clearLoginRateLimit('admin');
|
|
|
|
|
await logEvent('admin_login', 'admin', { ok: true }, await getRequestMeta());
|
|
|
|
|
redirect(`${managementBasePath}/admin/dashboard`);
|
|
|
|
|
@@ -232,7 +264,7 @@ export async function adminLogoutAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await clearAuthCookie();
|
|
|
|
|
@@ -245,18 +277,18 @@ export async function uploadFileAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await requireAuthenticatedUser();
|
|
|
|
|
const uploadedFile = uploadedFileFromForm(formData, 'file');
|
|
|
|
|
if (!uploadedFile || Number(uploadedFile.size || 0) <= 0) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Keine Datei hochgeladen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=upload`, 'Keine Datei hochgeladen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (maxUploadBytes > 0 && Number(uploadedFile.size || 0) > maxUploadBytes) {
|
|
|
|
|
redirectWithError(
|
|
|
|
|
`${managementBasePath}/dashboard`,
|
|
|
|
|
`${managementBasePath}/dashboard?tab=upload`,
|
|
|
|
|
`Datei überschreitet das Größenlimit (${maxUploadBytes} Bytes).`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@@ -285,7 +317,7 @@ export async function uploadFileAction(formData) {
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Upload fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=upload`, 'Upload fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -295,7 +327,7 @@ export async function uploadFileAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard`, 'Upload abgeschlossen.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard?tab=upload`, 'Upload abgeschlossen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function deleteOwnUploadAction(formData) {
|
|
|
|
|
@@ -308,7 +340,7 @@ export async function deleteOwnUploadAction(formData) {
|
|
|
|
|
const user = await requireAuthenticatedUser();
|
|
|
|
|
const uploadId = getUploadId(formData);
|
|
|
|
|
if (!uploadId) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Ungültige Upload-ID.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'Ungültige Upload-ID.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadEntry = await get(
|
|
|
|
|
@@ -317,7 +349,7 @@ export async function deleteOwnUploadAction(formData) {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!uploadEntry) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Upload nicht gefunden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'Upload nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@@ -327,20 +359,20 @@ export async function deleteOwnUploadAction(formData) {
|
|
|
|
|
|
|
|
|
|
await run('DELETE FROM uploads WHERE id = ?', [uploadEntry.id]);
|
|
|
|
|
await logEvent('delete', user.username, { id: uploadEntry.id }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard`, 'Upload gelöscht.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard?tab=files`, 'Upload gelöscht.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function extendOwnUploadAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await requireAuthenticatedUser();
|
|
|
|
|
const uploadId = getUploadId(formData);
|
|
|
|
|
if (!uploadId) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Ungültige Upload-ID.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'Ungültige Upload-ID.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadEntry = await get(
|
|
|
|
|
@@ -349,7 +381,7 @@ export async function extendOwnUploadAction(formData) {
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!uploadEntry) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Upload nicht gefunden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=files`, 'Upload nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extensionSeconds = parseHours(formData.get('extendHours'), uploadTtlSeconds);
|
|
|
|
|
@@ -366,7 +398,7 @@ export async function extendOwnUploadAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard`, 'Aufbewahrung aktualisiert.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard?tab=files`, 'Aufbewahrung aktualisiert.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createUploadRequestAction(formData) {
|
|
|
|
|
@@ -375,7 +407,7 @@ export async function createUploadRequestAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=requests`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const user = await requireAuthenticatedUser();
|
|
|
|
|
@@ -395,7 +427,7 @@ export async function createUploadRequestAction(formData) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!requestId) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Anfrage-ID konnte nicht erzeugt werden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=requests`, 'Anfrage-ID konnte nicht erzeugt werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@@ -404,7 +436,7 @@ export async function createUploadRequestAction(formData) {
|
|
|
|
|
[requestId, user.username, note || null, now, expiresAt]
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard`, 'Anfrage konnte nicht gespeichert werden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/dashboard?tab=requests`, 'Anfrage konnte nicht gespeichert werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -414,44 +446,77 @@ export async function createUploadRequestAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard`, 'Upload-Anfrage erstellt.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/dashboard?tab=requests`, 'Upload-Anfrage erstellt.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminCreateUserAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=create`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await requireAdminUser();
|
|
|
|
|
|
|
|
|
|
const username = String(formData.get('username') || '').trim();
|
|
|
|
|
const password = String(formData.get('password') || '');
|
|
|
|
|
const isAdmin = String(formData.get('isAdmin') || '') === '1';
|
|
|
|
|
if (!username || username.length > 200 || !password) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'Ungültige Eingabe.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=create`, 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const passwordHash = bcrypt.hashSync(password, 12);
|
|
|
|
|
try {
|
|
|
|
|
await run('INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, ?)', [
|
|
|
|
|
await run('INSERT INTO users (username, password_hash, created_at, is_admin) VALUES (?, ?, ?, ?)', [
|
|
|
|
|
username,
|
|
|
|
|
passwordHash,
|
|
|
|
|
Date.now(),
|
|
|
|
|
isAdmin ? 1 : 0,
|
|
|
|
|
]);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'Benutzername existiert bereits.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=create`, 'Benutzername existiert bereits.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent('admin_user_create', 'admin', { username }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users`, 'Benutzer erstellt.');
|
|
|
|
|
await logEvent('admin_user_create', 'admin', { username, is_admin: isAdmin }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users?tab=users`, 'Benutzer erstellt.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminSetUserAdminAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const actor = await requireAdminUser();
|
|
|
|
|
|
|
|
|
|
const username = String(formData.get('username') || '').trim();
|
|
|
|
|
const isAdmin = String(formData.get('isAdmin') || '') === '1';
|
|
|
|
|
if (!username) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actor.username === username && !isAdmin) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Eigene Adminrechte können nicht entzogen werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await run('UPDATE users SET is_admin = ? WHERE username = ?', [isAdmin ? 1 : 0, username]);
|
|
|
|
|
if (!result || result.changes < 1) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Benutzer nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent('admin_user_role', 'admin', { username, is_admin: isAdmin }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(
|
|
|
|
|
`${managementBasePath}/admin/users?tab=users`,
|
|
|
|
|
isAdmin ? 'Adminrechte vergeben.' : 'Adminrechte entzogen.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminResetUserAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await requireAdminUser();
|
|
|
|
|
@@ -459,32 +524,36 @@ export async function adminResetUserAction(formData) {
|
|
|
|
|
const username = String(formData.get('username') || '').trim();
|
|
|
|
|
const password = String(formData.get('password') || '');
|
|
|
|
|
if (!username || !password) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'Ungültige Eingabe.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const passwordHash = bcrypt.hashSync(password, 12);
|
|
|
|
|
await run('UPDATE users SET password_hash = ? WHERE username = ?', [passwordHash, username]);
|
|
|
|
|
await logEvent('admin_user_reset', 'admin', { username }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users`, 'Passwort aktualisiert.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users?tab=users`, 'Passwort aktualisiert.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminDeleteUserAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await requireAdminUser();
|
|
|
|
|
const actor = await requireAdminUser();
|
|
|
|
|
|
|
|
|
|
const username = String(formData.get('username') || '').trim();
|
|
|
|
|
if (!username) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users`, 'Ungültige Eingabe.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actor.username === username) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/users?tab=users`, 'Eigener Benutzer kann nicht gelöscht werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await run('DELETE FROM users WHERE username = ?', [username]);
|
|
|
|
|
await logEvent('admin_user_delete', 'admin', { username }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users`, 'Benutzer gelöscht.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/users?tab=users`, 'Benutzer gelöscht.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminDeleteUploadAction(formData) {
|
|
|
|
|
@@ -498,12 +567,12 @@ export async function adminDeleteUploadAction(formData) {
|
|
|
|
|
|
|
|
|
|
const uploadId = getUploadId(formData);
|
|
|
|
|
if (!uploadId) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'Ungültige Upload-ID.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Ungültige Upload-ID.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadEntry = await get('SELECT id, stored_path FROM uploads WHERE id = ?', [uploadId]);
|
|
|
|
|
if (!uploadEntry) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'Upload nicht gefunden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Upload nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@@ -513,26 +582,26 @@ export async function adminDeleteUploadAction(formData) {
|
|
|
|
|
|
|
|
|
|
await run('DELETE FROM uploads WHERE id = ?', [uploadEntry.id]);
|
|
|
|
|
await logEvent('delete', 'admin', { id: uploadEntry.id }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/dashboard`, 'Upload gelöscht.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Upload gelöscht.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminExtendUploadAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await requireAdminUser();
|
|
|
|
|
|
|
|
|
|
const uploadId = getUploadId(formData);
|
|
|
|
|
if (!uploadId) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'Ungültige Upload-ID.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Ungültige Upload-ID.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadEntry = await get('SELECT id, expires_at FROM uploads WHERE id = ?', [uploadId]);
|
|
|
|
|
if (!uploadEntry) {
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard`, 'Upload nicht gefunden.');
|
|
|
|
|
redirectWithError(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Upload nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extensionSeconds = parseHours(formData.get('extendHours'), uploadTtlSeconds);
|
|
|
|
|
@@ -549,7 +618,61 @@ export async function adminExtendUploadAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/dashboard`, 'Aufbewahrung aktualisiert.');
|
|
|
|
|
redirectWithSuccess(`${managementBasePath}/admin/dashboard?tab=uploads`, 'Aufbewahrung aktualisiert.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminSaveUploadToPathAction(formData) {
|
|
|
|
|
const requestedTab = String(formData.get('redirectTab') || 'uploads');
|
|
|
|
|
const redirectTab = ['uploads', 'requests'].includes(requestedTab) ? requestedTab : 'uploads';
|
|
|
|
|
const dashboardPath = `${managementBasePath}/admin/dashboard?tab=${redirectTab}`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await verifyCsrf(formData);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(dashboardPath, 'CSRF-Prüfung fehlgeschlagen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await requireAdminUser();
|
|
|
|
|
|
|
|
|
|
const uploadId = getUploadId(formData);
|
|
|
|
|
const rawTargetPath = String(formData.get('targetPath') || '').trim();
|
|
|
|
|
const targetRelativePath = sanitizeRelativePath(rawTargetPath);
|
|
|
|
|
if (!uploadId || !rawTargetPath || !targetRelativePath) {
|
|
|
|
|
redirectWithError(dashboardPath, 'Upload und Zielpfad sind erforderlich.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadEntry = await get(
|
|
|
|
|
'SELECT id, original_name, stored_name, stored_path FROM uploads WHERE id = ?',
|
|
|
|
|
[uploadId]
|
|
|
|
|
);
|
|
|
|
|
if (!uploadEntry) {
|
|
|
|
|
redirectWithError(dashboardPath, 'Upload nicht gefunden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileName = savedUploadFileName(uploadEntry.original_name, uploadEntry.stored_name);
|
|
|
|
|
const targetPath = await resolveSavedUploadTarget(targetRelativePath, fileName);
|
|
|
|
|
if (!targetPath) {
|
|
|
|
|
redirectWithError(dashboardPath, 'Ungültiger Zielpfad.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.cp(uploadEntry.stored_path, targetPath, { force: false, errorOnExist: true });
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(
|
|
|
|
|
dashboardPath,
|
|
|
|
|
'Upload konnte nicht gespeichert werden. Ziel existiert evtl. bereits.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const relativeTarget = sanitizeRelativePath(path.relative(resolveAdminPath(''), targetPath));
|
|
|
|
|
await logEvent(
|
|
|
|
|
'admin_upload_save',
|
|
|
|
|
'admin',
|
|
|
|
|
{ upload_id: uploadEntry.id, target: relativeTarget },
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(dashboardPath, 'Upload im Dateimanager gespeichert.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminMkdirAction(formData) {
|
|
|
|
|
@@ -565,19 +688,19 @@ export async function adminMkdirAction(formData) {
|
|
|
|
|
const folderName = String(formData.get('name') || '').trim();
|
|
|
|
|
|
|
|
|
|
if (!isValidNodeName(folderName)) {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Ungültiger Ordnername.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'folder'), 'Ungültiger Ordnername.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const basePath = resolveAdminPath(relativePath);
|
|
|
|
|
if (!basePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Ungültiger Pfad.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'folder'), 'Ungültiger Pfad.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targetPath = path.join(basePath, folderName);
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.mkdir(targetPath, { recursive: true });
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Ordner konnte nicht erstellt werden.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'folder'), 'Ordner konnte nicht erstellt werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -587,7 +710,7 @@ export async function adminMkdirAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(adminFilesHref(relativePath), 'Ordner erstellt.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(relativePath, 'content'), 'Ordner erstellt.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminUploadToPathAction(formData) {
|
|
|
|
|
@@ -602,30 +725,30 @@ export async function adminUploadToPathAction(formData) {
|
|
|
|
|
const relativePath = sanitizeRelativePath(formData.get('path'));
|
|
|
|
|
const basePath = resolveAdminPath(relativePath);
|
|
|
|
|
if (!basePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Ungültiger Pfad.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'upload'), 'Ungültiger Pfad.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uploadedFile = uploadedFileFromForm(formData, 'file');
|
|
|
|
|
if (!uploadedFile || Number(uploadedFile.size || 0) <= 0) {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Keine Datei hochgeladen.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'upload'), 'Keine Datei hochgeladen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (maxUploadBytes > 0 && Number(uploadedFile.size || 0) > maxUploadBytes) {
|
|
|
|
|
redirectWithError(
|
|
|
|
|
adminFilesHref(relativePath),
|
|
|
|
|
adminFilesTabHref(relativePath, 'upload'),
|
|
|
|
|
`Datei überschreitet das Größenlimit (${maxUploadBytes} Bytes).`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileName = safeBaseName(uploadedFile.name, 'upload');
|
|
|
|
|
if (!isValidNodeName(fileName)) {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Ungültiger Dateiname.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'upload'), 'Ungültiger Dateiname.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await writeUploadedFile(uploadedFile, path.join(basePath, fileName));
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(relativePath), 'Datei konnte nicht hochgeladen werden.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(relativePath, 'upload'), 'Datei konnte nicht hochgeladen werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -635,7 +758,7 @@ export async function adminUploadToPathAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(adminFilesHref(relativePath), 'Datei hochgeladen.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(relativePath, 'content'), 'Datei hochgeladen.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminRenamePathAction(formData) {
|
|
|
|
|
@@ -655,7 +778,7 @@ export async function adminRenamePathAction(formData) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isValidNodeName(newName)) {
|
|
|
|
|
redirectWithError(adminFilesHref(parentRelativePath(relativePath)), 'Ungültiger Name.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(parentRelativePath(relativePath), 'content'), 'Ungültiger Name.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourcePath = resolveAdminPath(relativePath);
|
|
|
|
|
@@ -667,7 +790,10 @@ export async function adminRenamePathAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.rename(sourcePath, targetPath);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(parentRelativePath(relativePath)), 'Pfad konnte nicht umbenannt werden.');
|
|
|
|
|
redirectWithError(
|
|
|
|
|
adminFilesTabHref(parentRelativePath(relativePath), 'content'),
|
|
|
|
|
'Pfad konnte nicht umbenannt werden.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -680,7 +806,7 @@ export async function adminRenamePathAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(adminFilesHref(parentRelativePath(relativePath)), 'Pfad umbenannt.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(parentRelativePath(relativePath), 'content'), 'Pfad umbenannt.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminDeletePathAction(formData) {
|
|
|
|
|
@@ -705,11 +831,14 @@ export async function adminDeletePathAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.rm(sourcePath, { recursive: true, force: true });
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(parentRelativePath(relativePath)), 'Pfad konnte nicht gelöscht werden.');
|
|
|
|
|
redirectWithError(
|
|
|
|
|
adminFilesTabHref(parentRelativePath(relativePath), 'content'),
|
|
|
|
|
'Pfad konnte nicht gelöscht werden.'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent('admin_delete', 'admin', { path: relativePath }, await getRequestMeta());
|
|
|
|
|
redirectWithSuccess(adminFilesHref(parentRelativePath(relativePath)), 'Pfad gelöscht.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(parentRelativePath(relativePath), 'content'), 'Pfad gelöscht.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminMovePathAction(formData) {
|
|
|
|
|
@@ -726,13 +855,13 @@ export async function adminMovePathAction(formData) {
|
|
|
|
|
const targetRelativePath = sanitizeRelativePath(formData.get('targetPath'));
|
|
|
|
|
|
|
|
|
|
if (!sourceRelativePath || !targetRelativePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Ungültige Eingabe.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourcePath = resolveAdminPath(sourceRelativePath);
|
|
|
|
|
const targetBasePath = resolveAdminPath(targetRelativePath);
|
|
|
|
|
if (!sourcePath || !targetBasePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Ungültiger Pfad.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Ungültiger Pfad.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targetPath = await resolveMoveCopyTarget(sourcePath, targetBasePath);
|
|
|
|
|
@@ -745,10 +874,10 @@ export async function adminMovePathAction(formData) {
|
|
|
|
|
await copyPath(sourcePath, targetPath);
|
|
|
|
|
await fs.promises.rm(sourcePath, { recursive: true, force: true });
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Pfad konnte nicht verschoben werden.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Pfad konnte nicht verschoben werden.');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Pfad konnte nicht verschoben werden.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Pfad konnte nicht verschoben werden.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -759,7 +888,7 @@ export async function adminMovePathAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(adminFilesHref(currentPath), 'Pfad verschoben.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(currentPath, 'content'), 'Pfad verschoben.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adminCopyPathAction(formData) {
|
|
|
|
|
@@ -776,13 +905,13 @@ export async function adminCopyPathAction(formData) {
|
|
|
|
|
const targetRelativePath = sanitizeRelativePath(formData.get('targetPath'));
|
|
|
|
|
|
|
|
|
|
if (!sourceRelativePath || !targetRelativePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Ungültige Eingabe.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Ungültige Eingabe.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourcePath = resolveAdminPath(sourceRelativePath);
|
|
|
|
|
const targetBasePath = resolveAdminPath(targetRelativePath);
|
|
|
|
|
if (!sourcePath || !targetBasePath) {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Ungültiger Pfad.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Ungültiger Pfad.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const targetPath = await resolveMoveCopyTarget(sourcePath, targetBasePath);
|
|
|
|
|
@@ -790,7 +919,7 @@ export async function adminCopyPathAction(formData) {
|
|
|
|
|
try {
|
|
|
|
|
await copyPath(sourcePath, targetPath);
|
|
|
|
|
} catch {
|
|
|
|
|
redirectWithError(adminFilesHref(currentPath), 'Pfad konnte nicht kopiert werden.');
|
|
|
|
|
redirectWithError(adminFilesTabHref(currentPath, 'content'), 'Pfad konnte nicht kopiert werden.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await logEvent(
|
|
|
|
|
@@ -800,5 +929,5 @@ export async function adminCopyPathAction(formData) {
|
|
|
|
|
await getRequestMeta()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
redirectWithSuccess(adminFilesHref(currentPath), 'Pfad kopiert.');
|
|
|
|
|
redirectWithSuccess(adminFilesTabHref(currentPath, 'content'), 'Pfad kopiert.');
|
|
|
|
|
}
|
|
|
|
|
|