refactor(config-export): simplify auth and add mirrors support
- Use authenticated context from requireAuth middleware instead of duplicating session verification logic - Remove unused cookie imports and constants (COOKIE_NAME, COOKIE_OPTIONS) - Add backupScheduleMirrorsTable to export for complete backup config - Include mirror-specific metadata keys (lastCopyAt, lastCopyStatus, lastCopyError)
This commit is contained in:
parent
dc5c88efcd
commit
62df1b1241
1 changed files with 22 additions and 25 deletions
|
|
@ -1,8 +1,7 @@
|
||||||
import { validator } from "hono-openapi";
|
import { validator } from "hono-openapi";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import type { Context } from "hono";
|
import type { Context } from "hono";
|
||||||
import { deleteCookie, getCookie } from "hono/cookie";
|
import { backupSchedulesTable, backupScheduleNotificationsTable, backupScheduleMirrorsTable, usersTable } from "../../db/schema";
|
||||||
import { backupSchedulesTable, backupScheduleNotificationsTable, usersTable } from "../../db/schema";
|
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import { RESTIC_PASS_FILE } from "../../core/constants";
|
import { RESTIC_PASS_FILE } from "../../core/constants";
|
||||||
|
|
@ -20,14 +19,6 @@ import {
|
||||||
} from "./config-export.dto";
|
} from "./config-export.dto";
|
||||||
import { requireAuth } from "../auth/auth.middleware";
|
import { requireAuth } from "../auth/auth.middleware";
|
||||||
|
|
||||||
const COOKIE_NAME = "session_id";
|
|
||||||
const COOKIE_OPTIONS = {
|
|
||||||
httpOnly: true,
|
|
||||||
secure: false,
|
|
||||||
sameSite: "lax" as const,
|
|
||||||
path: "/",
|
|
||||||
};
|
|
||||||
|
|
||||||
type ExportParams = {
|
type ExportParams = {
|
||||||
includeMetadata: boolean;
|
includeMetadata: boolean;
|
||||||
secretsMode: SecretsMode;
|
secretsMode: SecretsMode;
|
||||||
|
|
@ -36,8 +27,8 @@ type ExportParams = {
|
||||||
// Keys to exclude when metadata is not included
|
// Keys to exclude when metadata is not included
|
||||||
const METADATA_KEYS = {
|
const METADATA_KEYS = {
|
||||||
ids: ["id", "volumeId", "repositoryId", "scheduleId", "destinationId"],
|
ids: ["id", "volumeId", "repositoryId", "scheduleId", "destinationId"],
|
||||||
timestamps: ["createdAt", "updatedAt", "lastBackupAt", "nextBackupAt", "lastHealthCheck", "lastChecked"],
|
timestamps: ["createdAt", "updatedAt", "lastBackupAt", "nextBackupAt", "lastHealthCheck", "lastChecked", "lastCopyAt"],
|
||||||
runtimeState: ["status", "lastError", "lastBackupStatus", "lastBackupError", "hasDownloadedResticPassword"],
|
runtimeState: ["status", "lastError", "lastBackupStatus", "lastBackupError", "hasDownloadedResticPassword", "lastCopyStatus", "lastCopyError"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALL_METADATA_KEYS = [...METADATA_KEYS.ids, ...METADATA_KEYS.timestamps, ...METADATA_KEYS.runtimeState];
|
const ALL_METADATA_KEYS = [...METADATA_KEYS.ids, ...METADATA_KEYS.timestamps, ...METADATA_KEYS.runtimeState];
|
||||||
|
|
@ -66,29 +57,24 @@ function parseExportParamsFromBody(body: {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify password for export operation.
|
* Verify password for export operation.
|
||||||
* All exports require password verification for security.
|
* Requires requireAuth middleware to have already validated the session.
|
||||||
*/
|
*/
|
||||||
async function verifyExportPassword(
|
async function verifyExportPassword(
|
||||||
c: Context,
|
c: Context,
|
||||||
password: string
|
password: string
|
||||||
): Promise<{ valid: true; userId: number } | { valid: false; error: string }> {
|
): Promise<{ valid: true; userId: number } | { valid: false; error: string }> {
|
||||||
const sessionId = getCookie(c, COOKIE_NAME);
|
// requireAuth middleware ensures c.get('user') exists
|
||||||
if (!sessionId) {
|
const user = c.get("user");
|
||||||
|
if (!user) {
|
||||||
return { valid: false, error: "Not authenticated" };
|
return { valid: false, error: "Not authenticated" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await authService.verifySession(sessionId);
|
const isValid = await authService.verifyPassword(user.id, password);
|
||||||
if (!session) {
|
|
||||||
deleteCookie(c, COOKIE_NAME, COOKIE_OPTIONS);
|
|
||||||
return { valid: false, error: "Session expired" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = await authService.verifyPassword(session.user.id, password);
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return { valid: false, error: "Incorrect password" };
|
return { valid: false, error: "Incorrect password" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { valid: true, userId: session.user.id };
|
return { valid: true, userId: user.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -150,10 +136,11 @@ async function exportEntities<T extends Record<string, unknown>>(
|
||||||
return Promise.all(entities.map((e) => exportEntity(e as Record<string, unknown>, params)));
|
return Promise.all(entities.map((e) => exportEntity(e as Record<string, unknown>, params)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Transform backup schedules with resolved names and notifications */
|
/** Transform backup schedules with resolved names, notifications, and mirrors */
|
||||||
function transformBackupSchedules(
|
function transformBackupSchedules(
|
||||||
schedules: (typeof backupSchedulesTable.$inferSelect)[],
|
schedules: (typeof backupSchedulesTable.$inferSelect)[],
|
||||||
scheduleNotifications: (typeof backupScheduleNotificationsTable.$inferSelect)[],
|
scheduleNotifications: (typeof backupScheduleNotificationsTable.$inferSelect)[],
|
||||||
|
scheduleMirrors: (typeof backupScheduleMirrorsTable.$inferSelect)[],
|
||||||
volumeMap: Map<number, string>,
|
volumeMap: Map<number, string>,
|
||||||
repoMap: Map<string, string>,
|
repoMap: Map<string, string>,
|
||||||
notificationMap: Map<number, string>,
|
notificationMap: Map<number, string>,
|
||||||
|
|
@ -167,11 +154,19 @@ function transformBackupSchedules(
|
||||||
name: notificationMap.get(sn.destinationId) ?? null,
|
name: notificationMap.get(sn.destinationId) ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mirrors = scheduleMirrors
|
||||||
|
.filter((sm) => sm.scheduleId === schedule.id)
|
||||||
|
.map((sm) => ({
|
||||||
|
...filterMetadataOut(sm as unknown as Record<string, unknown>, params.includeMetadata),
|
||||||
|
repository: repoMap.get(sm.repositoryId) ?? null,
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...filterMetadataOut(schedule as Record<string, unknown>, params.includeMetadata),
|
...filterMetadataOut(schedule as Record<string, unknown>, params.includeMetadata),
|
||||||
volume: volumeMap.get(schedule.volumeId) ?? null,
|
volume: volumeMap.get(schedule.volumeId) ?? null,
|
||||||
repository: repoMap.get(schedule.repositoryId) ?? null,
|
repository: repoMap.get(schedule.repositoryId) ?? null,
|
||||||
notifications: assignments,
|
notifications: assignments,
|
||||||
|
mirrors,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -197,13 +192,14 @@ export const configExportController = new Hono()
|
||||||
const includePasswordHash = body.includePasswordHash === true;
|
const includePasswordHash = body.includePasswordHash === true;
|
||||||
|
|
||||||
// Use services to fetch data
|
// Use services to fetch data
|
||||||
const [volumes, repositories, backupSchedulesRaw, notifications, scheduleNotifications, users] =
|
const [volumes, repositories, backupSchedulesRaw, notifications, scheduleNotifications, scheduleMirrors, users] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
volumeService.listVolumes(),
|
volumeService.listVolumes(),
|
||||||
repositoriesService.listRepositories(),
|
repositoriesService.listRepositories(),
|
||||||
backupsService.listSchedules(),
|
backupsService.listSchedules(),
|
||||||
notificationsService.listDestinations(),
|
notificationsService.listDestinations(),
|
||||||
db.select().from(backupScheduleNotificationsTable),
|
db.select().from(backupScheduleNotificationsTable),
|
||||||
|
db.select().from(backupScheduleMirrorsTable),
|
||||||
db.select().from(usersTable),
|
db.select().from(usersTable),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -214,6 +210,7 @@ export const configExportController = new Hono()
|
||||||
const backupSchedules = transformBackupSchedules(
|
const backupSchedules = transformBackupSchedules(
|
||||||
backupSchedulesRaw,
|
backupSchedulesRaw,
|
||||||
scheduleNotifications,
|
scheduleNotifications,
|
||||||
|
scheduleMirrors,
|
||||||
volumeMap,
|
volumeMap,
|
||||||
repoMap,
|
repoMap,
|
||||||
notificationMap,
|
notificationMap,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue