Index: server/entry.ts
===================================================================
--- server/entry.ts	(revision 66bf4fdd2573ef9770d4f2d8dc1e5a8f3f842a7c)
+++ server/entry.ts	(revision b69759dc4c8f1a000afc0d0c5c5afcc6868641fb)
@@ -2,5 +2,5 @@
 import { authjsHandler, authjsSessionMiddleware } from "./authjs-handler";
 import { dbMiddleware } from "./db-middleware";
-import { telefuncHandler } from "./telefunc-handler";
+import telefuncHandler from "./telefunc-handler";
 import { apply, serve } from "@photonjs/hono";
 import { Hono } from "hono";
Index: server/telefunc-handler.ts
===================================================================
--- server/telefunc-handler.ts	(revision 66bf4fdd2573ef9770d4f2d8dc1e5a8f3f842a7c)
+++ server/telefunc-handler.ts	(revision b69759dc4c8f1a000afc0d0c5c5afcc6868641fb)
@@ -2,6 +2,7 @@
 import { enhance, type UniversalHandler } from "@universal-middleware/core";
 import { telefunc } from "telefunc";
+import type { Session } from "@auth/core/types";
 
-export const telefuncHandler: UniversalHandler = enhance(
+const telefuncHandler: UniversalHandler = enhance(
   async (request, context, runtime) => {
     const httpResponse = await telefunc({
@@ -10,7 +11,9 @@
       body: await request.text(),
       context: {
-        ...(context as { db: Database }),
-        ...runtime,
-      },
+          ...(context as { db: Database; session?: Session | null }),
+          ...runtime,
+          session: (context as { session?: Session | null }).session ?? null,
+          request,
+      }
     });
     const { body, statusCode, contentType } = httpResponse;
@@ -29,2 +32,3 @@
   },
 );
+export default telefuncHandler
Index: server/telefunc/ctx.ts
===================================================================
--- server/telefunc/ctx.ts	(revision b69759dc4c8f1a000afc0d0c5c5afcc6868641fb)
+++ server/telefunc/ctx.ts	(revision b69759dc4c8f1a000afc0d0c5c5afcc6868641fb)
@@ -0,0 +1,39 @@
+import { Abort, getContext } from "telefunc";
+import * as drizzleQueries from "../../database/drizzle/queries";
+
+export function ctx() {
+    return getContext();
+}
+
+export function parseSessionUserId(sessionUserId: unknown): number | null {
+    if (typeof sessionUserId !== "string" && typeof sessionUserId !== "number") return null;
+    const n = Number(sessionUserId);
+    return Number.isInteger(n) && n > 0 ? n : null;
+}
+
+export function requireUser() {
+    const c = ctx();
+    const userId = parseSessionUserId(c.session?.user?.id);
+    if (!userId) throw Abort();
+    return { c, userId };
+}
+
+export function getAuthState() {
+    const c = ctx();
+    const userId = parseSessionUserId(c.session?.user?.id);
+    return {
+        isLoggedIn: Boolean(userId),
+        userId: userId ?? null,
+        username: c.session?.user?.name ?? null,
+        session: c.session,
+    };
+}
+
+export async function requireAdmin() {
+    const { c, userId } = requireUser();
+
+    const isAdmin = await drizzleQueries.isAdmin(c.db, userId);
+
+    if (!isAdmin) throw Abort();
+    return { c, userId };
+}
