From 37f19a1a632e0ad130f8141cdb6876ffdf5b766d Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 19 May 2026 19:28:32 +0200 Subject: [PATCH] feat(auth): allow skipping forced recovery key download --- app/client/lib/recovery-key-skip.ts | 11 +++++++++ .../auth/routes/download-recovery-key.tsx | 23 ++++++++++++++----- app/client/modules/auth/routes/login.tsx | 20 ++++++++++++---- .../modules/settings/routes/settings.tsx | 2 +- app/routes/(dashboard)/route.tsx | 3 ++- 5 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 app/client/lib/recovery-key-skip.ts diff --git a/app/client/lib/recovery-key-skip.ts b/app/client/lib/recovery-key-skip.ts new file mode 100644 index 00000000..c51b08c4 --- /dev/null +++ b/app/client/lib/recovery-key-skip.ts @@ -0,0 +1,11 @@ +const RECOVERY_KEY_DOWNLOAD_SKIPPED_KEY = "zerobyte:recovery-key-download-skipped"; + +export function hasSkippedRecoveryKeyDownload() { + if (typeof window === "undefined") return false; + + return window.sessionStorage.getItem(RECOVERY_KEY_DOWNLOAD_SKIPPED_KEY) === "true"; +} + +export function skipRecoveryKeyDownload() { + window.sessionStorage.setItem(RECOVERY_KEY_DOWNLOAD_SKIPPED_KEY, "true"); +} diff --git a/app/client/modules/auth/routes/download-recovery-key.tsx b/app/client/modules/auth/routes/download-recovery-key.tsx index 2ff2b6ae..971aa27c 100644 --- a/app/client/modules/auth/routes/download-recovery-key.tsx +++ b/app/client/modules/auth/routes/download-recovery-key.tsx @@ -9,10 +9,11 @@ import { Input } from "~/client/components/ui/input"; import { Label } from "~/client/components/ui/label"; import { downloadResticPasswordMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { parseError } from "~/client/lib/errors"; +import { skipRecoveryKeyDownload } from "~/client/lib/recovery-key-skip"; import { useNavigate } from "@tanstack/react-router"; const RECOVERY_KEY_CREDENTIAL_REQUIRED_MESSAGE = - "Downloading the recovery key requires a local credential password. Ask an operator to run `bun run cli reset-password` for your user, then sign in with that password and try again."; + "Downloading the recovery key requires a local credential password. Ask an operator to run `docker exec -it zerobyte bun run cli reset-password` for your user, then sign in with that password and try again."; type Props = { hasCredentialPassword: boolean; @@ -56,11 +57,12 @@ export function DownloadRecoveryKeyPage({ hasCredentialPassword }: Props) { } setBlockedMessage(null); - downloadResticPassword.mutate({ - body: { - password, - }, - }); + downloadResticPassword.mutate({ body: { password } }); + }; + + const handleSkip = () => { + skipRecoveryKeyDownload(); + void navigate({ to: "/volumes", replace: true }); }; return ( @@ -114,6 +116,15 @@ export function DownloadRecoveryKeyPage({ hasCredentialPassword }: Props) { Download Recovery Key )} + diff --git a/app/client/modules/auth/routes/login.tsx b/app/client/modules/auth/routes/login.tsx index 9e9aa9f3..76340543 100644 --- a/app/client/modules/auth/routes/login.tsx +++ b/app/client/modules/auth/routes/login.tsx @@ -10,6 +10,7 @@ import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "~/clie import { Label } from "~/client/components/ui/label"; import { authClient } from "~/client/lib/auth-client"; import { logger } from "~/client/lib/logger"; +import { hasSkippedRecoveryKeyDownload } from "~/client/lib/recovery-key-skip"; import { decodeLoginError, getLoginErrorDescription } from "~/client/lib/sso-errors"; import { ResetPasswordDialog } from "../components/reset-password-dialog"; import { useNavigate } from "@tanstack/react-router"; @@ -77,7 +78,7 @@ export function LoginPage({ error }: LoginPageProps = {}) { } const d = await authClient.getSession(); - if (data.user && !d.data?.user.hasDownloadedResticPassword) { + if (data.user && !d.data?.user.hasDownloadedResticPassword && !hasSkippedRecoveryKeyDownload()) { void navigate({ to: "/download-recovery-key" }); } else { void navigate({ to: "/volumes" }); @@ -113,7 +114,11 @@ export function LoginPage({ error }: LoginPageProps = {}) { if (data) { toast.success("Login successful"); const session = await authClient.getSession(); - if (session.data?.user && !session.data.user.hasDownloadedResticPassword) { + if ( + session.data?.user && + !session.data.user.hasDownloadedResticPassword && + !hasSkippedRecoveryKeyDownload() + ) { void navigate({ to: "/download-recovery-key" }); } else { void navigate({ to: "/volumes" }); @@ -130,7 +135,10 @@ export function LoginPage({ error }: LoginPageProps = {}) { if (requires2FA) { return ( - +
@@ -199,7 +207,11 @@ export function LoginPage({ error }: LoginPageProps = {}) {
-
+
{errorDescription}
{ const headers = getRequestHeaders(); @@ -45,7 +46,7 @@ export const Route = createFileRoute("/(dashboard)")({ }), ]); - if (authContext.user && !authContext.user.hasDownloadedResticPassword) { + if (authContext.user && !authContext.user.hasDownloadedResticPassword && !hasSkippedRecoveryKeyDownload()) { throw redirect({ to: "/download-recovery-key" }); }