feat(auth): allow skipping forced recovery key download
This commit is contained in:
parent
c071596151
commit
37f19a1a63
5 changed files with 47 additions and 12 deletions
11
app/client/lib/recovery-key-skip.ts
Normal file
11
app/client/lib/recovery-key-skip.ts
Normal file
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={handleSkip}
|
||||
disabled={downloadResticPassword.isPending}
|
||||
className="w-full"
|
||||
>
|
||||
Skip
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</AuthLayout>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<AuthLayout title="Two-Factor Authentication" description="Enter the 6-digit code from your authenticator app">
|
||||
<AuthLayout
|
||||
title="Two-Factor Authentication"
|
||||
description="Enter the 6-digit code from your authenticator app"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4 flex flex-col items-center">
|
||||
<Label htmlFor="totp-code">Authentication code</Label>
|
||||
|
|
@ -199,7 +207,11 @@ export function LoginPage({ error }: LoginPageProps = {}) {
|
|||
<AuthLayout title="Login to your account" description="Enter your credentials below to login to your account">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div className={cn("rounded-md border border-destructive/50 p-3 text-sm", { hidden: !errorDescription })}>
|
||||
<div
|
||||
className={cn("rounded-md border border-destructive/50 p-3 text-sm", {
|
||||
hidden: !errorDescription,
|
||||
})}
|
||||
>
|
||||
{errorDescription}
|
||||
</div>
|
||||
<FormField
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import { useOrganizationContext } from "~/client/hooks/use-org-context";
|
|||
import { cn } from "~/client/lib/utils";
|
||||
|
||||
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 = {
|
||||
appContext: AppContext;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { getOrganizationContext } from "~/server/lib/functions/organization-cont
|
|||
import { getServerConstants } from "~/server/lib/functions/server-constants";
|
||||
import { userHasCredentialPassword } from "~/server/modules/auth/helpers";
|
||||
import { authService } from "~/server/modules/auth/auth.service";
|
||||
import { hasSkippedRecoveryKeyDownload } from "~/client/lib/recovery-key-skip";
|
||||
|
||||
export const fetchUser = createServerFn({ method: "GET" }).handler(async () => {
|
||||
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" });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue