diff --git a/apps/remix/app/entry.server.tsx b/apps/remix/app/entry.server.tsx index 7f28001f4..895ccba93 100644 --- a/apps/remix/app/entry.server.tsx +++ b/apps/remix/app/entry.server.tsx @@ -7,10 +7,11 @@ import { createReadableStreamFromReadable } from '@react-router/node'; import { isbot } from 'isbot'; import type { RenderToPipeableStreamOptions } from 'react-dom/server'; import { renderToPipeableStream } from 'react-dom/server'; -import type { AppLoadContext, EntryContext } from 'react-router'; +import type { EntryContext, RouterContextProvider } from 'react-router'; import { ServerRouter } from 'react-router'; import { langCookie } from './storage/lang-cookie.server'; +import { nonceContext } from './utils/nonce'; export const streamTimeout = 5_000; @@ -19,7 +20,7 @@ export default async function handleRequest( responseStatusCode: number, responseHeaders: Headers, routerContext: EntryContext, - loadContext: AppLoadContext, + loadContext: RouterContextProvider, ) { let language = await langCookie.parse(request.headers.get('cookie') ?? ''); @@ -33,7 +34,7 @@ export default async function handleRequest( // scripts it injects (route manifest, hydration data, module preloads). // The same nonce is also exposed to the React tree via the root loader so // our own inline scripts/styles can carry it. - const nonce = loadContext.nonce || undefined; + const nonce = loadContext.get(nonceContext) || undefined; return new Promise((resolve, reject) => { let shellRendered = false; diff --git a/apps/remix/app/middleware/admin.ts b/apps/remix/app/middleware/admin.ts new file mode 100644 index 000000000..0eef2c90d --- /dev/null +++ b/apps/remix/app/middleware/admin.ts @@ -0,0 +1,13 @@ +import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session'; +import { isAdmin } from '@documenso/lib/utils/is-admin'; +import { type MiddlewareFunction, redirect } from 'react-router'; + +export const adminMiddleware: MiddlewareFunction = async ({ request }, next) => { + const { user } = await getOptionalSession(request); + + if (!user || !isAdmin(user)) { + throw redirect('/'); + } + + return next(); +}; diff --git a/apps/remix/app/middleware/nonce.ts b/apps/remix/app/middleware/nonce.ts new file mode 100644 index 000000000..916d5232d --- /dev/null +++ b/apps/remix/app/middleware/nonce.ts @@ -0,0 +1,8 @@ +import type { MiddlewareFunction } from 'react-router'; + +import { getRequestNonce } from '../../server/load-context'; +import { nonceContext } from '../utils/nonce'; + +export const nonceMiddleware: MiddlewareFunction = ({ context }) => { + context.set(nonceContext, getRequestNonce()); +}; diff --git a/apps/remix/app/root.tsx b/apps/remix/app/root.tsx index baffaa6ec..e317068df 100644 --- a/apps/remix/app/root.tsx +++ b/apps/remix/app/root.tsx @@ -23,13 +23,16 @@ import { useMatches, } from 'react-router'; import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from 'remix-themes'; +import { nonceMiddleware } from '~/middleware/nonce'; import type { Route } from './+types/root'; import stylesheet from './app.css?url'; import { GenericErrorLayout } from './components/general/generic-error-layout'; import { langCookie } from './storage/lang-cookie.server'; import { themeSessionResolver } from './storage/theme-session.server'; import { appMetaTags } from './utils/meta'; -import { nonce } from './utils/nonce'; +import { nonce, nonceContext } from './utils/nonce'; + +export const middleware = [nonceMiddleware]; export const links: Route.LinksFunction = () => [{ rel: 'stylesheet', href: stylesheet }]; @@ -74,7 +77,7 @@ export async function loader({ context, request }: Route.LoaderArgs) { // Surface the per-request CSP nonce produced by `securityHeadersMiddleware` so all // SSR-rendered