Files
freno-dev/src/server/api/utils.ts
2026-01-07 16:22:31 -05:00

102 lines
2.7 KiB
TypeScript

import { initTRPC, TRPCError } from "@trpc/server";
import type { APIEvent } from "@solidjs/start/server";
import { getCookie } from "vinxi/http";
import { env } from "~/env/server";
import { logVisit, enrichAnalyticsEntry } from "~/server/analytics";
import { getRequestIP } from "vinxi/http";
import { getAuthSession } from "~/server/session-helpers";
export type Context = {
event: APIEvent;
userId: string | null;
privilegeLevel: "anonymous" | "user" | "admin";
};
async function createContextInner(event: APIEvent): Promise<Context> {
// Get auth session from Vinxi encrypted session
const session = await getAuthSession(event.nativeEvent);
let userId: string | null = null;
let privilegeLevel: "anonymous" | "user" | "admin" = "anonymous";
if (session && session.userId) {
userId = session.userId;
privilegeLevel = session.isAdmin ? "admin" : "user";
}
const req = event.nativeEvent.node?.req || event.nativeEvent;
const path = req.url || event.request?.url || "unknown";
const method = req.method || event.request?.method || "GET";
const userAgent =
req.headers?.["user-agent"] ||
event.request?.headers?.get("user-agent") ||
undefined;
const referrer =
req.headers?.referer ||
req.headers?.referrer ||
event.request?.headers?.get("referer") ||
undefined;
const ipAddress = getRequestIP(event.nativeEvent) || undefined;
const sessionId = getCookie(event.nativeEvent, "session_id") || undefined;
// Don't log the performance logging endpoint itself to avoid circular tracking
if (!path.includes("analytics.logPerformance")) {
logVisit(
enrichAnalyticsEntry({
userId,
path,
method,
userAgent,
referrer,
ipAddress,
sessionId
})
);
}
return {
event,
userId,
privilegeLevel
};
}
export const createTRPCContext = (event: APIEvent) => {
return createContextInner(event);
};
export const t = initTRPC.context<Context>().create();
export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure;
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.userId || ctx.privilegeLevel === "anonymous") {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
}
return next({
ctx: {
...ctx,
userId: ctx.userId
}
});
});
const enforceUserIsAdmin = t.middleware(({ ctx, next }) => {
if (ctx.privilegeLevel !== "admin") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Admin access required"
});
}
return next({
ctx: {
...ctx,
userId: ctx.userId!
}
});
});
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
export const adminProcedure = t.procedure.use(enforceUserIsAdmin);