feat: auth guard on routes
This commit is contained in:
parent
df252be49c
commit
1bf7f49178
11 changed files with 47 additions and 38 deletions
23
studio/frontend/src/app/auth-guards.ts
Normal file
23
studio/frontend/src/app/auth-guards.ts
Normal 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() });
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue