Implement role-based access

This commit is contained in:
Moshfegh Hamedani 2026-02-12 09:19:36 -08:00
parent 6715c2073e
commit e37142221f
6 changed files with 47 additions and 3 deletions

View file

@ -47,5 +47,6 @@ The client proxies `/api/*` requests to the server via Vite config.
- **Client config**: `client/src/lib/auth-client.ts` — exports `signIn`, `signOut`, `useSession` - **Client config**: `client/src/lib/auth-client.ts` — exports `signIn`, `signOut`, `useSession`
- **Middleware**: `server/src/middleware/require-auth.ts``requireAuth` guard that sets `req.user` and `req.session` - **Middleware**: `server/src/middleware/require-auth.ts``requireAuth` guard that sets `req.user` and `req.session`
- **Route protection (client)**: `ProtectedRoute` component wraps authenticated routes; redirects to `/login` if unauthenticated - **Route protection (client)**: `ProtectedRoute` component wraps authenticated routes; redirects to `/login` if unauthenticated
- **Admin route protection (client)**: `AdminRoute` component wraps admin-only routes; redirects non-admins to `/`
- **Sign-up is disabled** — users are seeded via `prisma/seed.ts` - **Sign-up is disabled** — users are seeded via `prisma/seed.ts`
- **User roles**: `admin` and `agent` (defined as Prisma enum, default `agent`) - **User roles**: `admin` and `agent` (defined as Prisma enum, default `agent`)

View file

@ -1,8 +1,10 @@
import { Navigate, Route, Routes } from "react-router"; import { Navigate, Route, Routes } from "react-router";
import ProtectedRoute from "./components/ProtectedRoute"; import ProtectedRoute from "./components/ProtectedRoute";
import AdminRoute from "./components/AdminRoute";
import Layout from "./components/Layout"; import Layout from "./components/Layout";
import LoginPage from "./pages/LoginPage"; import LoginPage from "./pages/LoginPage";
import HomePage from "./pages/HomePage"; import HomePage from "./pages/HomePage";
import UsersPage from "./pages/UsersPage";
function App() { function App() {
return ( return (
@ -11,6 +13,9 @@ function App() {
<Route element={<ProtectedRoute />}> <Route element={<ProtectedRoute />}>
<Route element={<Layout />}> <Route element={<Layout />}>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route element={<AdminRoute />}>
<Route path="/users" element={<UsersPage />} />
</Route>
</Route> </Route>
</Route> </Route>
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />

View file

@ -0,0 +1,20 @@
import { Navigate, Outlet } from "react-router";
import { useSession } from "../lib/auth-client";
export default function AdminRoute() {
const { data: session, isPending } = useSession();
if (isPending) {
return (
<div className="flex items-center justify-center h-screen text-lg text-muted-foreground">
Loading...
</div>
);
}
if (session?.user?.role !== "admin") {
return <Navigate to="/" replace />;
}
return <Outlet />;
}

View file

@ -1,4 +1,4 @@
import { Outlet, useNavigate } from "react-router"; import { Link, Outlet, useNavigate } from "react-router";
import { signOut, useSession } from "../lib/auth-client"; import { signOut, useSession } from "../lib/auth-client";
export default function Layout() { export default function Layout() {
@ -13,7 +13,14 @@ export default function Layout() {
return ( return (
<div className="min-h-screen flex flex-col"> <div className="min-h-screen flex flex-col">
<nav className="flex items-center justify-between bg-background border-b px-6 h-14"> <nav className="flex items-center justify-between bg-background border-b px-6 h-14">
<div className="flex items-center gap-6">
<span className="text-lg font-bold">Helpdesk</span> <span className="text-lg font-bold">Helpdesk</span>
{session?.user?.role === "admin" && (
<Link to="/users" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
Users
</Link>
)}
</div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<span className="text-sm text-muted-foreground">{session?.user?.name}</span> <span className="text-sm text-muted-foreground">{session?.user?.name}</span>
<button <button

View file

@ -1,3 +1,7 @@
import { createAuthClient } from "better-auth/react"; import { createAuthClient } from "better-auth/react";
import { inferAdditionalFields } from "better-auth/client/plugins";
import type { auth } from "../../../server/src/lib/auth";
export const { signIn, signOut, useSession } = createAuthClient(); export const { signIn, signOut, useSession } = createAuthClient({
plugins: [inferAdditionalFields<typeof auth>()],
});

View file

@ -0,0 +1,7 @@
export default function UsersPage() {
return (
<div>
<h1 className="text-2xl font-bold">Users</h1>
</div>
);
}