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:
parent
91a3112d8b
commit
a3685a9e68
13 changed files with 196 additions and 24 deletions
|
|
@ -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 });
|
||||
|
|
|
|||
44
apps/web/src/routes/login.tsx
Normal file
44
apps/web/src/routes/login.tsx
Normal 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 });
|
||||
|
|
@ -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" });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue