fix: force recovery key redownload after possible truncated download
This commit is contained in:
parent
0f5a6823ec
commit
66ebc249ca
4 changed files with 59 additions and 6 deletions
|
|
@ -59,9 +59,9 @@ export function DownloadRecoveryKeyPage() {
|
||||||
<AlertTriangle className="size-5" />
|
<AlertTriangle className="size-5" />
|
||||||
<AlertTitle>Important: Save This File Securely</AlertTitle>
|
<AlertTitle>Important: Save This File Securely</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
Your Restic password is essential for recovering your backup data. If you lose access to this server
|
Your Restic password is essential for recovering your backup data. If you previously downloaded this
|
||||||
without this file, your backups will be unrecoverable. Store it in a password manager or encrypted
|
file, replace that saved copy with the new download. If you lose access to this server without this
|
||||||
storage.
|
file, your backups will be unrecoverable. Store it in a password manager or encrypted storage.
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createServerFn } from "@tanstack/react-start";
|
import { createServerFn } from "@tanstack/react-start";
|
||||||
import { getCookie, getRequestHeaders } from "@tanstack/react-start/server";
|
import { getCookie, getRequestHeaders } from "@tanstack/react-start/server";
|
||||||
import { Layout } from "~/client/components/layout";
|
import { Layout } from "~/client/components/layout";
|
||||||
|
|
@ -39,6 +39,10 @@ export const Route = createFileRoute("/(dashboard)")({
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (authContext.user && !authContext.user.hasDownloadedResticPassword) {
|
||||||
|
throw redirect({ to: "/download-recovery-key" });
|
||||||
|
}
|
||||||
|
|
||||||
return authContext;
|
return authContext;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { v00003 } from "./migrations/00003-assign-organization";
|
||||||
import { v00004 } from "./migrations/00004-concat-path-name";
|
import { v00004 } from "./migrations/00004-concat-path-name";
|
||||||
import { v00005 } from "./migrations/00005-split-backup-include-paths";
|
import { v00005 } from "./migrations/00005-split-backup-include-paths";
|
||||||
import { v00006 } from "./migrations/00006-map-smb-files-to-container-uid-gid";
|
import { v00006 } from "./migrations/00006-map-smb-files-to-container-uid-gid";
|
||||||
|
import { v00007 } from "./migrations/00007-require-recovery-key-redownload";
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { appMetadataTable, usersTable } from "../../db/schema";
|
import { appMetadataTable, usersTable } from "../../db/schema";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
|
|
@ -17,7 +18,12 @@ const recordMigrationCheckpoint = async (version: string): Promise<void> => {
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.insert(appMetadataTable)
|
.insert(appMetadataTable)
|
||||||
.values({ key, value: JSON.stringify({ completedAt: new Date().toISOString() }), createdAt: now, updatedAt: now })
|
.values({
|
||||||
|
key,
|
||||||
|
value: JSON.stringify({ completedAt: new Date().toISOString() }),
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: appMetadataTable.key,
|
target: appMetadataTable.key,
|
||||||
set: { value: JSON.stringify({ completedAt: new Date().toISOString() }), updatedAt: now },
|
set: { value: JSON.stringify({ completedAt: new Date().toISOString() }), updatedAt: now },
|
||||||
|
|
@ -39,7 +45,7 @@ type MigrationEntity = {
|
||||||
dependsOn?: string[];
|
dependsOn?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const registry: MigrationEntity[] = [v00001, v00002, v00003, v00004, v00005, v00006];
|
const registry: MigrationEntity[] = [v00001, v00002, v00003, v00004, v00005, v00006, v00007];
|
||||||
|
|
||||||
export const runMigrations = async () => {
|
export const runMigrations = async () => {
|
||||||
const userCount = await db.select({ count: sql<number>`count(*)` }).from(usersTable);
|
const userCount = await db.select({ count: sql<number>`count(*)` }).from(usersTable);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
|
import { logger } from "@zerobyte/core/node";
|
||||||
|
import { db } from "../../../db/db";
|
||||||
|
import { account, member, usersTable } from "../../../db/schema";
|
||||||
|
import { toMessage } from "~/server/utils/errors";
|
||||||
|
|
||||||
|
const execute = async () => {
|
||||||
|
const errors: Array<{ name: string; error: string }> = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const affectedUsers = await db
|
||||||
|
.select({ id: usersTable.id })
|
||||||
|
.from(usersTable)
|
||||||
|
.innerJoin(account, and(eq(account.userId, usersTable.id), eq(account.providerId, "credential")))
|
||||||
|
.innerJoin(member, and(eq(member.userId, usersTable.id), inArray(member.role, ["owner", "admin"])))
|
||||||
|
.where(eq(usersTable.hasDownloadedResticPassword, true));
|
||||||
|
const affectedUserIds = [...new Set(affectedUsers.map((user) => user.id))];
|
||||||
|
|
||||||
|
if (affectedUserIds.length > 0) {
|
||||||
|
await db
|
||||||
|
.update(usersTable)
|
||||||
|
.set({ hasDownloadedResticPassword: false })
|
||||||
|
.where(inArray(usersTable.id, affectedUserIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Migration 00007-require-recovery-key-redownload marked ${affectedUserIds.length} users for recovery key re-download.`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
errors.push({
|
||||||
|
name: "recovery-key-redownload",
|
||||||
|
error: toMessage(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: errors.length === 0, errors };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const v00007 = {
|
||||||
|
execute,
|
||||||
|
id: "00007-require-recovery-key-redownload",
|
||||||
|
type: "maintenance" as const,
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue