mirror of
https://github.com/mosh-hamedani/helpdesk.git
synced 2026-05-21 11:58:19 +02:00
Implement role-based access
This commit is contained in:
parent
6715c2073e
commit
e37142221f
|
|
@ -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`)
|
||||||
|
|
|
||||||
|
|
@ -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 />} />
|
||||||
|
|
|
||||||
20
client/src/components/AdminRoute.tsx
Normal file
20
client/src/components/AdminRoute.tsx
Normal 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 />;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>()],
|
||||||
|
});
|
||||||
|
|
|
||||||
7
client/src/pages/UsersPage.tsx
Normal file
7
client/src/pages/UsersPage.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default function UsersPage() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold">Users</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue