From f5e2110ff461eb4f567354fc48c6feed9c100cd9 Mon Sep 17 00:00:00 2001 From: Regalijan Date: Thu, 12 Mar 2026 02:20:16 -0400 Subject: [PATCH] Add user tagging for logged-in staff users --- app/entry.client.tsx | 1 + app/root.tsx | 18 +++++++++++++++++- functions/_middleware.ts | 32 +++++++++++++++++++++++++++++--- functions/api/st.ts | 23 +++++++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 functions/api/st.ts diff --git a/app/entry.client.tsx b/app/entry.client.tsx index d9918c1..79bcc13 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -22,6 +22,7 @@ Sentry.init({ replaysSessionSampleRate: 0.02, sendDefaultPii: true, tracesSampleRate: 0.1, + tunnel: "/api/st", }); function ClientCacheProvider({ children }: { children: ReactNode }) { diff --git a/app/root.tsx b/app/root.tsx index 4519e34..a0436da 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -27,7 +27,11 @@ import Navigation from "../components/Navigation.js"; import { type ReactNode, StrictMode, useContext, useEffect } from "react"; import theme from "../theme.js"; import { withEmotionCache } from "@emotion/react"; -import { captureRemixErrorBoundaryError, withSentry } from "@sentry/remix"; +import { + captureRemixErrorBoundaryError, + setUser, + withSentry, +} from "@sentry/remix"; export function ErrorBoundary() { const error = useRouteError() as ErrorResponse; @@ -226,6 +230,18 @@ function DocumentWrapper(props: { function App() { const loaderData = useLoaderData(); + if ( + loaderData.id && + [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].find( + (p) => loaderData.permissions & (1 << p), + ) + ) + setUser({ + email: loaderData.email, + id: loaderData.id, + username: loaderData.username, + }); + return ( diff --git a/functions/_middleware.ts b/functions/_middleware.ts index ca08f8d..26c3601 100644 --- a/functions/_middleware.ts +++ b/functions/_middleware.ts @@ -31,8 +31,21 @@ async function generateTokenHash(token: string) { async function refreshAuth(context: RequestContext) { const { current_user: currentUser } = context.data; - if (!currentUser || currentUser.refresh_at > Date.now()) - return await context.next(); + if (!currentUser) return await context.next(); + + if (currentUser.refresh_at > Date.now()) { + if ( + [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].find( + (p) => currentUser.permissions & (1 << p), + ) + ) + Sentry.setUser({ + email: currentUser.email, + id: currentUser.id, + ip_address: context.request.headers.get("cf-connecting-ip"), + username: currentUser.username, + }); + } const oauthData = await context.env.DATA.get( `oauthcredentials_${currentUser.id}`, @@ -95,6 +108,18 @@ async function refreshAuth(context: RequestContext) { : undefined, ); + if ( + [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].find( + (p) => currentUser.permissions & (1 << p), + ) + ) + Sentry.setUser({ + email: currentUser.email, + id: currentUser.id, + ip_address: context.request.headers.get("cf-connecting-ip"), + username: currentUser.username, + }); + const tokenHash = await generateTokenHash(context.data.sid); await context.env.DATA.put(`auth_${tokenHash}`, JSON.stringify(userData), { @@ -273,7 +298,8 @@ async function setAuth(context: RequestContext) { async function setBody(context: RequestContext) { if ( ["PATCH", "POST", "PUT"].includes(context.request.method) && - !context.request.url.endsWith("/api/infractions/new") + !context.request.url.endsWith("/api/infractions/new") && + !context.request.url.endsWith("/api/st") ) { if ( !context.request.headers diff --git a/functions/api/st.ts b/functions/api/st.ts new file mode 100644 index 0000000..87b33c9 --- /dev/null +++ b/functions/api/st.ts @@ -0,0 +1,23 @@ +import { jsonError } from "../common.js"; + +export async function onRequestPost(context: RequestContext) { + const dsn = context.request.headers.get("dsn"); + + if (!dsn || dsn !== context.env.DSN) return jsonError("Bad or no DSN", 400); + + const sentryUrl = new URL(dsn); + await fetch(`https://${sentryUrl.host}/api${sentryUrl.pathname}/envelope`, { + body: context.request.body, + headers: { + "content-type": "application/x-sentry-envelope", + "x-forwarded-for": context.request.headers.get( + "cf-connecting-ip", + ) as string, + }, + method: "POST", + }); + + return new Response(null, { + status: 204, + }); +}