feat: auth guard on routes

This commit is contained in:
shine1i 2026-02-11 14:01:30 +01:00
commit 1bf7f49178
11 changed files with 47 additions and 38 deletions

View file

@ -0,0 +1,23 @@
import { redirect } from "@tanstack/react-router";
import {
getPostAuthRoute,
hasAuthToken,
hasRefreshToken,
refreshSession,
} from "@/features/auth";
async function hasActiveSession(): Promise<boolean> {
if (hasAuthToken()) return true;
if (!hasRefreshToken()) return false;
return refreshSession();
}
export async function requireAuth(): Promise<void> {
if (await hasActiveSession()) return;
throw redirect({ to: "/login" });
}
export async function requireGuest(): Promise<void> {
if (!(await hasActiveSession())) return;
throw redirect({ to: getPostAuthRoute() });
}

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const ChatPage = lazy(() =>
@ -9,5 +10,6 @@ const ChatPage = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/chat",
beforeLoad: () => requireAuth(),
component: ChatPage,
});

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const ExportPage = lazy(() =>
@ -11,5 +12,6 @@ const ExportPage = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/export",
beforeLoad: () => requireAuth(),
component: ExportPage,
});

View file

@ -7,11 +7,13 @@ import {
CardTitle,
} from "@/components/ui/card";
import { createRoute } from "@tanstack/react-router";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/grid-test",
beforeLoad: () => requireAuth(),
component: GridTestPage,
});

View file

@ -1,10 +1,12 @@
import { ComponentExample } from "@/components/component-example";
import { createRoute } from "@tanstack/react-router";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/",
beforeLoad: () => requireAuth(),
component: HomePage,
});

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireGuest } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const LoginPage = lazy(() =>
@ -9,5 +10,6 @@ const LoginPage = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/login",
beforeLoad: () => requireGuest(),
component: LoginPage,
});

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const WizardLayout = lazy(() =>
@ -11,5 +12,6 @@ const WizardLayout = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/onboarding",
beforeLoad: () => requireAuth(),
component: WizardLayout,
});

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireGuest } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const SignupPage = lazy(() =>
@ -11,5 +12,6 @@ const SignupPage = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/signup",
beforeLoad: () => requireGuest(),
component: SignupPage,
});

View file

@ -1,5 +1,6 @@
import { createRoute } from "@tanstack/react-router";
import { lazy } from "react";
import { requireAuth } from "../auth-guards";
import { Route as rootRoute } from "./__root";
const StudioPage = lazy(() =>
@ -11,5 +12,6 @@ const StudioPage = lazy(() =>
export const Route = createRoute({
getParentRoute: () => rootRoute,
path: "/studio",
beforeLoad: () => requireAuth(),
component: StudioPage,
});

View file

@ -33,11 +33,9 @@ type AuthFormProps = {
export function AuthForm({ mode }: AuthFormProps): ReactElement | null {
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [username, setUsername] = useState("admin");
const [setupToken, setSetupToken] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
const [statusLoading, setStatusLoading] = useState(true);
const [initialized, setInitialized] = useState<boolean | null>(null);
@ -106,10 +104,6 @@ export function AuthForm({ mode }: AuthFormProps): ReactElement | null {
event.preventDefault();
setError(null);
if (!isLoginMode && password !== confirmPassword) {
setError("Passwords not match.");
return;
}
if (!isLoginMode && !setupToken.trim()) {
setError("Setup token required.");
return;
@ -221,37 +215,6 @@ export function AuthForm({ mode }: AuthFormProps): ReactElement | null {
</div>
)}
{!isLoginMode && (
<div className="space-y-2">
<Label htmlFor="confirm-password">Confirm password</Label>
<div className="relative">
<Input
id="confirm-password"
type={showConfirmPassword ? "text" : "password"}
className="pr-10"
autoComplete="new-password"
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
minLength={8}
required
/>
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-0 top-0 h-full px-3 text-muted-foreground hover:bg-transparent"
onClick={() => setShowConfirmPassword((prev) => !prev)}
>
{showConfirmPassword ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</Button>
</div>
</div>
)}
{helperText && (
<p className="text-center text-sm text-amber-600">{helperText}</p>
)}

View file

@ -1,3 +1,10 @@
export { LoginPage } from "./login-page";
export { SignupPage } from "./signup-page";
export { isOnboardingDone, markOnboardingDone } from "./session";
export { refreshSession } from "./api";
export {
getPostAuthRoute,
hasAuthToken,
hasRefreshToken,
isOnboardingDone,
markOnboardingDone,
} from "./session";