feat(api): add auth middleware to protect game endpoints

- Add requireAuth middleware using Better Auth session validation
- Apply to all game routes (start, answer)
- Unauthenticated requests return 401
This commit is contained in:
lila 2026-04-12 13:38:32 +02:00
parent 91a3112d8b
commit a3685a9e68
13 changed files with 196 additions and 24 deletions

View file

@ -1,20 +1,51 @@
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import {
createRootRoute,
Link,
Outlet,
useNavigate,
} from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { useSession, signOut } from "../lib/auth-client";
const RootLayout = () => (
<>
<div className="p-2 flex gap-2">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>{" "}
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
);
const RootLayout = () => {
const { data: session } = useSession();
const navigate = useNavigate();
return (
<>
<div className="p-2 flex gap-2 items-center">
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
<div className="ml-auto">
{session ? (
<button
className="text-sm text-gray-600 hover:text-gray-900"
onClick={async () => {
await signOut();
navigate({ to: "/" });
}}
>
Sign out ({session.user.name})
</button>
) : (
<Link
to="/login"
className="text-sm text-blue-600 hover:text-blue-800"
>
Sign in
</Link>
)}
</div>
</div>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
);
};
export const Route = createRootRoute({ component: RootLayout });

View file

@ -0,0 +1,44 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { signIn, useSession } from "../lib/auth-client";
const LoginPage = () => {
const { data: session, isPending } = useSession();
const navigate = useNavigate();
if (isPending) return <div className="p-4">Loading...</div>;
if (session) {
navigate({ to: "/" });
return null;
}
return (
<div className="flex flex-col items-center justify-center gap-4 p-8">
<h1 className="text-2xl font-bold">Sign in to Glossa</h1>
<button
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
onClick={() =>
signIn.social({
provider: "github",
callbackURL: "http://localhost:5173",
})
}
>
Continue with GitHub
</button>
<button
className="w-64 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-500"
onClick={() =>
signIn.social({
provider: "google",
callbackURL: "http://localhost:5173",
})
}
>
Continue with Google
</button>
</div>
);
};
export const Route = createFileRoute("/login")({ component: LoginPage });

View file

@ -1,9 +1,10 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { useState, useCallback } from "react";
import type { GameSession, GameRequest, AnswerResult } from "@glossa/shared";
import { QuestionCard } from "../components/game/QuestionCard";
import { ScoreScreen } from "../components/game/ScoreScreen";
import { GameSetup } from "../components/game/GameSetup";
import { authClient } from "../lib/auth-client";
function Play() {
const [gameSession, setGameSession] = useState<GameSession | null>(null);
@ -105,4 +106,12 @@ function Play() {
);
}
export const Route = createFileRoute("/play")({ component: Play });
export const Route = createFileRoute("/play")({
component: Play,
beforeLoad: async () => {
const { data: session } = await authClient.getSession();
if (!session) {
throw redirect({ to: "/login" });
}
},
});