diff --git a/apps/remix/app/root.tsx b/apps/remix/app/root.tsx index bebf6534d..2c3c7f223 100644 --- a/apps/remix/app/root.tsx +++ b/apps/remix/app/root.tsx @@ -13,6 +13,7 @@ import { useLocation, } from 'react-router'; import { ThemeProvider } from 'remix-themes'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { SessionProvider } from '@documenso/lib/client-only/providers/session'; import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n'; @@ -82,7 +83,9 @@ export const links: Route.LinksFunction = () => [ // }; // } -export async function loader({ request, context }: Route.LoaderArgs) { +export async function loader({ request }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { getTheme } = await themeSessionResolver(request); let lang: SupportedLanguageCodes = await langCookie.parse(request.headers.get('cookie') ?? ''); @@ -95,7 +98,7 @@ export async function loader({ request, context }: Route.LoaderArgs) { { lang, theme: getTheme(), - session: context.session, + session, __ENV__: Object.fromEntries( Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_')), // Todo: I'm pretty sure this will leak? ), diff --git a/apps/remix/app/routes/_authenticated+/_layout.tsx b/apps/remix/app/routes/_authenticated+/_layout.tsx index cea725807..9646a7c88 100644 --- a/apps/remix/app/routes/_authenticated+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/_layout.tsx @@ -1,4 +1,5 @@ -import { Outlet, redirect } from 'react-router'; +import { Outlet } from 'react-router'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { getLimits } from '@documenso/ee/server-only/limits/client'; import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/client'; @@ -11,29 +12,25 @@ import { VerifyEmailBanner } from '~/components/general/verify-email-banner'; import type { Route } from './+types/_layout'; -export const loader = async ({ request, context }: Route.LoaderArgs) => { - const { session } = context; - - if (!session) { - throw redirect('/signin'); - } +export const loader = async ({ request }: Route.LoaderArgs) => { + const { user, teams, currentTeam } = getLoaderSession(); const requestHeaders = Object.fromEntries(request.headers.entries()); // Todo: Should only load this on first render. const [limits, banner] = await Promise.all([ - getLimits({ headers: requestHeaders, teamId: session.currentTeam?.id }), + getLimits({ headers: requestHeaders, teamId: currentTeam?.id }), getSiteSettings().then((settings) => settings.find((setting) => setting.id === SITE_SETTINGS_BANNER_ID), ), ]); return { - user: session.user, - teams: session.teams, + user, + teams, banner, limits, - teamId: session.currentTeam?.id, + teamId: currentTeam?.id, }; }; diff --git a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx index 3b64519a4..8d4a407e7 100644 --- a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx @@ -1,16 +1,14 @@ import { Trans } from '@lingui/macro'; import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react'; import { Link, Outlet, redirect, useLocation } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; -import type { Route } from './+types/_layout'; - -export function loader({ context }: Route.LoaderArgs) { - const { user } = getRequiredLoaderSession(context); +export function loader() { + const { user } = getLoaderSession(); if (!user || !isAdmin(user)) { throw redirect('/documents'); diff --git a/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx b/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx index c4a006984..2bb84d6f2 100644 --- a/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx +++ b/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx @@ -3,7 +3,7 @@ import { useLingui } from '@lingui/react'; import { DocumentStatus, TeamMemberRole } from '@prisma/client'; import { ChevronLeft, Clock9, Users2 } from 'lucide-react'; import { Link, redirect } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import { useSession } from '@documenso/lib/client-only/providers/session'; @@ -34,8 +34,8 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/$id._index'; -export async function loader({ params, context }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderSession(context); +export async function loader({ params }: Route.LoaderArgs) { + const { user, currentTeam: team } = getLoaderSession(); const { id } = params; diff --git a/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx b/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx index 77b15bb27..9bf123aae 100644 --- a/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx +++ b/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx @@ -2,7 +2,7 @@ import { Plural, Trans } from '@lingui/macro'; import { DocumentStatus as InternalDocumentStatus, TeamMemberRole } from '@prisma/client'; import { ChevronLeft, Users2 } from 'lucide-react'; import { Link, redirect } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; @@ -17,8 +17,8 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/$id.edit'; -export async function loader({ params, context }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderSession(context); +export async function loader({ params }: Route.LoaderArgs) { + const { user, currentTeam: team } = getLoaderSession(); const { id } = params; diff --git a/apps/remix/app/routes/_authenticated+/documents+/$id.logs.tsx b/apps/remix/app/routes/_authenticated+/documents+/$id.logs.tsx index 5dbc00a69..7f887679d 100644 --- a/apps/remix/app/routes/_authenticated+/documents+/$id.logs.tsx +++ b/apps/remix/app/routes/_authenticated+/documents+/$id.logs.tsx @@ -5,7 +5,7 @@ import type { Recipient } from '@prisma/client'; import { ChevronLeft } from 'lucide-react'; import { DateTime } from 'luxon'; import { Link, redirect } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document'; @@ -22,10 +22,10 @@ import { DocumentLogsTable } from '~/components/tables/document-logs-table'; import type { Route } from './+types/$id.logs'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { const { id } = params; - const { user, currentTeam: team } = getRequiredLoaderSession(context); + const { user, currentTeam: team } = getLoaderSession(); const documentId = Number(id); diff --git a/apps/remix/app/routes/_authenticated+/settings+/public-profile+/index.tsx b/apps/remix/app/routes/_authenticated+/settings+/public-profile+/index.tsx index de5eb9375..1276b37d5 100644 --- a/apps/remix/app/routes/_authenticated+/settings+/public-profile+/index.tsx +++ b/apps/remix/app/routes/_authenticated+/settings+/public-profile+/index.tsx @@ -4,7 +4,7 @@ import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import type { TemplateDirectLink } from '@prisma/client'; import { TemplateType } from '@prisma/client'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { getUserPublicProfile } from '@documenso/lib/server-only/user/get-user-public-profile'; @@ -43,8 +43,8 @@ const teamProfileText = { templatesSubtitle: msg`Show templates in your team public profile for your audience to sign and get started quickly`, }; -export async function loader({ context }: Route.LoaderArgs) { - const { user } = getRequiredLoaderSession(context); +export async function loader() { + const { user } = getLoaderSession(); const { profile } = await getUserPublicProfile({ userId: user.id, diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_index.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_index.tsx index de665bd2d..0683f5066 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_index.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_index.tsx @@ -1,13 +1,13 @@ import { redirect } from 'react-router'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { formatDocumentsPath } from '@documenso/lib/utils/teams'; -import type { Route } from './+types/_index'; - -export function loader({ context }: Route.LoaderArgs) { - if (!context.session?.currentTeam) { +export function loader() { + const { currentTeam } = getLoaderSession(); + if (!currentTeam) { throw redirect('/documents'); } - throw redirect(formatDocumentsPath(context.session.currentTeam.url)); + throw redirect(formatDocumentsPath(currentTeam.url)); } diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx index e7abffcfe..a52a32b29 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx @@ -3,7 +3,7 @@ import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { ChevronLeft } from 'lucide-react'; import { Link, Outlet, isRouteErrorResponse, redirect, useNavigate } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import { AppErrorCode } from '@documenso/lib/errors/app-error'; @@ -14,8 +14,8 @@ import { TeamProvider } from '~/providers/team'; import type { Route } from './+types/_layout'; -export const loader = ({ context }: Route.LoaderArgs) => { - const { currentTeam } = getRequiredLoaderSession(context); +export const loader = () => { + const { currentTeam } = getLoaderSession(); if (!currentTeam) { throw redirect('/documents'); diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/_layout.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/_layout.tsx index ea28f85d7..bef091059 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/_layout.tsx @@ -1,16 +1,14 @@ import { Trans } from '@lingui/macro'; import { Outlet } from 'react-router'; -import { getRequiredLoaderTeamSession } from 'server/utils/get-loader-session'; +import { getLoaderTeamSession } from 'server/utils/get-loader-session'; import { canExecuteTeamAction } from '@documenso/lib/utils/teams'; import { TeamSettingsNavDesktop } from '~/components/general/teams/team-settings-nav-desktop'; import { TeamSettingsNavMobile } from '~/components/general/teams/team-settings-nav-mobile'; -import type { Route } from '../+types/_layout'; - -export function loader({ context }: Route.LoaderArgs) { - const { currentTeam: team } = getRequiredLoaderTeamSession(context); +export function loader() { + const { currentTeam: team } = getLoaderTeamSession(); if (!team || !canExecuteTeamAction('MANAGE_TEAM', team.currentTeamMember.role)) { throw new Response(null, { status: 401 }); // Unauthorized. diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/billing.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/billing.tsx index 5886af1a0..2d3c07fef 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/billing.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/billing.tsx @@ -1,7 +1,7 @@ import { Plural, Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { DateTime } from 'luxon'; -import { getRequiredLoaderTeamSession } from 'server/utils/get-loader-session'; +import { getLoaderTeamSession } from 'server/utils/get-loader-session'; import type Stripe from 'stripe'; import { match } from 'ts-pattern'; @@ -15,8 +15,8 @@ import { TeamSettingsBillingInvoicesTable } from '~/components/tables/team-setti import type { Route } from './+types/billing'; -export async function loader({ context }: Route.LoaderArgs) { - const { currentTeam: team } = getRequiredLoaderTeamSession(context); +export async function loader() { + const { currentTeam: team } = getLoaderTeamSession(); let teamSubscription: Stripe.Subscription | null = null; diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/public-profile.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/public-profile.tsx index d1954b8ea..dd1c7d5c7 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/public-profile.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/public-profile.tsx @@ -1,13 +1,11 @@ -import { getRequiredLoaderTeamSession } from 'server/utils/get-loader-session'; +import { getLoaderTeamSession } from 'server/utils/get-loader-session'; import { getTeamPublicProfile } from '@documenso/lib/server-only/team/get-team-public-profile'; import PublicProfilePage from '~/routes/_authenticated+/settings+/public-profile+/index'; -import type { Route } from './+types/public-profile'; - -export async function loader({ context }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderTeamSession(context); +export async function loader() { + const { user, currentTeam: team } = getLoaderTeamSession(); const { profile } = await getTeamPublicProfile({ userId: user.id, diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/tokens.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/tokens.tsx index 05021959d..ae637162f 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/tokens.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings+/tokens.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro'; import { useLingui } from '@lingui/react'; import { DateTime } from 'luxon'; -import { getRequiredLoaderTeamSession } from 'server/utils/get-loader-session'; +import { getLoaderTeamSession } from 'server/utils/get-loader-session'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { getTeamTokens } from '@documenso/lib/server-only/public-api/get-all-team-tokens'; @@ -12,8 +12,8 @@ import { ApiTokenForm } from '~/components/forms/token'; import type { Route } from './+types/tokens'; -export async function loader({ context }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderTeamSession(context); +export async function loader() { + const { user, currentTeam: team } = getLoaderTeamSession(); const tokens = await getTeamTokens({ userId: user.id, teamId: team.id }).catch(() => null); diff --git a/apps/remix/app/routes/_authenticated+/templates+/$id._index.tsx b/apps/remix/app/routes/_authenticated+/templates+/$id._index.tsx index 52505040a..a99a1cb20 100644 --- a/apps/remix/app/routes/_authenticated+/templates+/$id._index.tsx +++ b/apps/remix/app/routes/_authenticated+/templates+/$id._index.tsx @@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'; import { DocumentSigningOrder, SigningStatus } from '@prisma/client'; import { ChevronLeft, LucideEdit } from 'lucide-react'; import { Link, redirect, useNavigate } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams'; @@ -24,8 +24,8 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/$id._index'; -export async function loader({ params, context }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderSession(context); +export async function loader({ params }: Route.LoaderArgs) { + const { user, currentTeam: team } = getLoaderSession(); const { id } = params; diff --git a/apps/remix/app/routes/_authenticated+/templates+/$id.edit.tsx b/apps/remix/app/routes/_authenticated+/templates+/$id.edit.tsx index 74d75b794..9fb00e6d4 100644 --- a/apps/remix/app/routes/_authenticated+/templates+/$id.edit.tsx +++ b/apps/remix/app/routes/_authenticated+/templates+/$id.edit.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro'; import { ChevronLeft } from 'lucide-react'; import { Link, redirect } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; @@ -15,8 +15,8 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import { TemplateDirectLinkDialogWrapper } from '../../../components/dialogs/template-direct-link-dialog-wrapper'; import type { Route } from './+types/$id.edit'; -export async function loader({ context, params }: Route.LoaderArgs) { - const { user, currentTeam: team } = getRequiredLoaderSession(context); +export async function loader({ params }: Route.LoaderArgs) { + const { user, currentTeam: team } = getLoaderSession(); const { id } = params; diff --git a/apps/remix/app/routes/_index.tsx b/apps/remix/app/routes/_index.tsx index 6bc005462..13a7291e3 100644 --- a/apps/remix/app/routes/_index.tsx +++ b/apps/remix/app/routes/_index.tsx @@ -1,9 +1,10 @@ import { redirect } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; -import type { Route } from './+types/_index'; +export function loader() { + const session = getOptionalLoaderSession(); -export function loader({ context }: Route.LoaderArgs) { - if (context.session) { + if (session) { throw redirect('/documents'); } diff --git a/apps/remix/app/routes/_profile+/_layout.tsx b/apps/remix/app/routes/_profile+/_layout.tsx index 9c9d883c1..92492a01b 100644 --- a/apps/remix/app/routes/_profile+/_layout.tsx +++ b/apps/remix/app/routes/_profile+/_layout.tsx @@ -6,24 +6,15 @@ import { ChevronLeft } from 'lucide-react'; import { Link, Outlet } from 'react-router'; import LogoIcon from '@documenso/assets/logo_icon.png'; +import { useSession } from '@documenso/lib/client-only/providers/session'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Header as AuthenticatedHeader } from '~/components/general/app-header'; -import { Logo } from '~/components/general/branding-logo'; +import { BrandingLogo } from '~/components/general/branding-logo'; -import type { Route } from './+types/_layout'; - -export function loader({ context }: Route.LoaderArgs) { - const { session } = context; - - return { - session, - }; -} - -export default function PublicProfileLayout({ loaderData }: Route.ComponentProps) { - const { session } = loaderData; +export default function PublicProfileLayout() { + const session = useSession(); const [scrollY, setScrollY] = useState(0); @@ -53,7 +44,7 @@ export default function PublicProfileLayout({ loaderData }: Route.ComponentProps to="/" className="focus-visible:ring-ring ring-offset-background rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 md:inline" > - + context.session?.user !== null) + .with(DocumentAccessAuth.ACCOUNT, () => session?.user !== null) .with(null, () => true) .exhaustive(); @@ -67,6 +70,7 @@ export default function DirectTemplatePage() { const data = useSuperLoaderData(); + // Should not be possible for directLink to be null. if (!data.isAccessAuthValid) { return ; } diff --git a/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx b/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx index 8a21da5b4..c0a12bcdc 100644 --- a/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx +++ b/apps/remix/app/routes/_recipient+/sign.$token+/_index.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'; import { DocumentStatus, SigningStatus } from '@prisma/client'; import { Clock8 } from 'lucide-react'; import { Link, redirect } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import signingCelebration from '@documenso/assets/images/signing-celebration.png'; import { useOptionalSession } from '@documenso/lib/client-only/providers/session'; @@ -25,14 +26,16 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/_index'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { throw new Response('Not Found', { status: 404 }); } - const user = context.session?.user; + const user = session?.user; const [document, fields, recipient, completedFields] = await Promise.all([ getDocumentAndSenderByToken({ diff --git a/apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx b/apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx index f5e0b5db3..9acbd3fe2 100644 --- a/apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx +++ b/apps/remix/app/routes/_recipient+/sign.$token+/complete.tsx @@ -3,6 +3,7 @@ import { useLingui } from '@lingui/react'; import { DocumentStatus, FieldType, RecipientRole } from '@prisma/client'; import { CheckCircle2, Clock8, FileSearch } from 'lucide-react'; import { Link } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import signingCelebration from '@documenso/assets/images/signing-celebration.png'; @@ -27,14 +28,16 @@ import { DocumentSigningAuthPageView } from '~/components/general/document-signi import type { Route } from './+types/complete'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { throw new Response('Not Found', { status: 404 }); } - const user = context.session?.user; + const user = session?.user; const document = await getDocumentAndSenderByToken({ token, diff --git a/apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx b/apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx index ff5b853e8..1ffc34a1b 100644 --- a/apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx +++ b/apps/remix/app/routes/_recipient+/sign.$token+/rejected.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'; import { FieldType } from '@prisma/client'; import { XCircle } from 'lucide-react'; import { Link } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { useOptionalSession } from '@documenso/lib/client-only/providers/session'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; @@ -16,14 +17,16 @@ import { truncateTitle } from '~/utils/truncate-title'; import type { Route } from './+types/rejected'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { throw new Response('Not Found', { status: 404 }); } - const user = context.session?.user; + const user = session?.user; const document = await getDocumentAndSenderByToken({ token, diff --git a/apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx b/apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx index c29011238..675888272 100644 --- a/apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx +++ b/apps/remix/app/routes/_recipient+/sign.$token+/waiting.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'; import type { Team } from '@prisma/client'; import { DocumentStatus } from '@prisma/client'; import { Link, redirect } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; @@ -12,7 +13,9 @@ import { Button } from '@documenso/ui/primitives/button'; import type { Route } from './+types/waiting'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { @@ -34,7 +37,7 @@ export async function loader({ params, context }: Route.LoaderArgs) { let isOwnerOrTeamMember = false; - const user = context.session?.user; + const user = session?.user; let team: Team | null = null; if (user) { diff --git a/apps/remix/app/routes/_unauthenticated+/signin.tsx b/apps/remix/app/routes/_unauthenticated+/signin.tsx index 202718dd2..891bbc58c 100644 --- a/apps/remix/app/routes/_unauthenticated+/signin.tsx +++ b/apps/remix/app/routes/_unauthenticated+/signin.tsx @@ -1,5 +1,6 @@ import { Trans } from '@lingui/macro'; import { Link, redirect } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { IS_GOOGLE_SSO_ENABLED, @@ -10,14 +11,14 @@ import { env } from '@documenso/lib/utils/env'; import { SignInForm } from '~/components/forms/signin'; -import type { Route } from './+types/signin'; - export function meta() { return [{ title: 'Sign In' }]; } -export function loader({ context }: Route.LoaderArgs) { - if (context.session) { +export function loader() { + const session = getOptionalLoaderSession(); + + if (session) { throw redirect('/documents'); } } diff --git a/apps/remix/app/routes/_unauthenticated+/team.decline.$token.tsx b/apps/remix/app/routes/_unauthenticated+/team.decline.$token.tsx index ad3363ded..0af2fd569 100644 --- a/apps/remix/app/routes/_unauthenticated+/team.decline.$token.tsx +++ b/apps/remix/app/routes/_unauthenticated+/team.decline.$token.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'; import { TeamMemberInviteStatus } from '@prisma/client'; import { DateTime } from 'luxon'; import { Link } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; import { declineTeamInvitation } from '@documenso/lib/server-only/team/decline-team-invitation'; @@ -11,7 +12,9 @@ import { Button } from '@documenso/ui/primitives/button'; import type { Route } from './+types/team.decline.$token'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { @@ -71,7 +74,7 @@ export async function loader({ params, context }: Route.LoaderArgs) { } as const; } - const isSessionUserTheInvitedUser = user.id === context.session?.user.id; + const isSessionUserTheInvitedUser = user.id === session?.user.id; return { state: 'Success', diff --git a/apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx b/apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx index a06f98dcb..27064a9e2 100644 --- a/apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx +++ b/apps/remix/app/routes/_unauthenticated+/team.invite.$token.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'; import { TeamMemberInviteStatus } from '@prisma/client'; import { DateTime } from 'luxon'; import { Link } from 'react-router'; +import { getOptionalLoaderSession } from 'server/utils/get-loader-session'; import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt'; import { acceptTeamInvitation } from '@documenso/lib/server-only/team/accept-team-invitation'; @@ -11,7 +12,9 @@ import { Button } from '@documenso/ui/primitives/button'; import type { Route } from './+types/team.invite.$token'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { + const session = getOptionalLoaderSession(); + const { token } = params; if (!token) { @@ -74,7 +77,7 @@ export async function loader({ params, context }: Route.LoaderArgs) { } as const; } - const isSessionUserTheInvitedUser = user.id === context.session?.user.id; + const isSessionUserTheInvitedUser = user.id === session?.user.id; return { state: 'Success', diff --git a/apps/remix/app/routes/embed+/direct.$url.tsx b/apps/remix/app/routes/embed+/direct.$url.tsx index 2f5387233..dec8b0584 100644 --- a/apps/remix/app/routes/embed+/direct.$url.tsx +++ b/apps/remix/app/routes/embed+/direct.$url.tsx @@ -1,5 +1,5 @@ import { data } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; @@ -17,7 +17,7 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/direct.$url'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { if (!params.url) { throw new Response('Not found', { status: 404 }); } @@ -48,7 +48,7 @@ export async function loader({ params, context }: Route.LoaderArgs) { ); } - const { user } = getRequiredLoaderSession(context); + const { user } = getLoaderSession(); const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ documentAuth: template.authOptions, diff --git a/apps/remix/app/routes/embed+/sign.$url.tsx b/apps/remix/app/routes/embed+/sign.$url.tsx index 432f85a3b..ef11dcfef 100644 --- a/apps/remix/app/routes/embed+/sign.$url.tsx +++ b/apps/remix/app/routes/embed+/sign.$url.tsx @@ -1,6 +1,6 @@ import { DocumentStatus } from '@prisma/client'; import { data } from 'react-router'; -import { getRequiredLoaderSession } from 'server/utils/get-loader-session'; +import { getLoaderSession } from 'server/utils/get-loader-session'; import { match } from 'ts-pattern'; import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; @@ -20,14 +20,14 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; import type { Route } from './+types/sign.$url'; -export async function loader({ params, context }: Route.LoaderArgs) { +export async function loader({ params }: Route.LoaderArgs) { if (!params.url) { throw new Response('Not found', { status: 404 }); } const token = params.url; - const { user } = getRequiredLoaderSession(context); + const { user } = getLoaderSession(); const [document, fields, recipient] = await Promise.all([ getDocumentAndSenderByToken({ diff --git a/apps/remix/server/load-context.ts b/apps/remix/server/context.ts similarity index 75% rename from apps/remix/server/load-context.ts rename to apps/remix/server/context.ts index ff1726384..dbc1c48de 100644 --- a/apps/remix/server/load-context.ts +++ b/apps/remix/server/context.ts @@ -1,24 +1,27 @@ +import type { Context, Next } from 'hono'; + import { extractSessionCookieFromHeaders } from '@documenso/auth/server/lib/session/session-cookies'; import { getSession } from '@documenso/auth/server/lib/utils/get-session'; +import type { AppSession } from '@documenso/lib/client-only/providers/session'; import { type TGetTeamByUrlResponse, getTeamByUrl } from '@documenso/lib/server-only/team/get-team'; import { type TGetTeamsResponse, getTeams } from '@documenso/lib/server-only/team/get-teams'; -import { extractRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; +import { + type RequestMetadata, + extractRequestMetadata, +} from '@documenso/lib/universal/extract-request-metadata'; import { AppLogger } from '@documenso/lib/utils/debugger'; -type GetLoadContextArgs = { - request: Request; +const logger = new AppLogger('Middleware'); + +export type AppContext = { + requestMetadata: RequestMetadata; + session: AppSession | null; }; -declare module 'react-router' { - interface AppLoadContext extends Awaited> {} -} - -const logger = new AppLogger('Context'); - -export async function getLoadContext(args: GetLoadContextArgs) { +export const appContext = async (c: Context, next: Next) => { const initTime = Date.now(); - const request = args.request; + const request = c.req.raw; const url = new URL(request.url); const noSessionCookie = extractSessionCookieFromHeaders(request.headers) === null; @@ -26,10 +29,12 @@ export async function getLoadContext(args: GetLoadContextArgs) { if (!isPageRequest(request) || noSessionCookie || blacklistedPathsRegex.test(url.pathname)) { logger.log('Pathname ignored', url.pathname); - return { + setAppContext(c, { requestMetadata: extractRequestMetadata(request), session: null, - }; + }); + + return next(); } const splitUrl = url.pathname.replace('.data', '').split('/'); @@ -37,7 +42,7 @@ export async function getLoadContext(args: GetLoadContextArgs) { let team: TGetTeamByUrlResponse | null = null; let teams: TGetTeamsResponse = []; - const session = await getSession(args.request); + const session = await getSession(c); if (session.isAuthenticated) { let teamUrl = null; @@ -58,7 +63,7 @@ export async function getLoadContext(args: GetLoadContextArgs) { const endTime = Date.now(); logger.log(`Pathname accepted in ${endTime - initTime}ms`, url.pathname); - return { + setAppContext(c, { requestMetadata: extractRequestMetadata(request), session: session.isAuthenticated ? { @@ -68,8 +73,14 @@ export async function getLoadContext(args: GetLoadContextArgs) { teams, } : null, - }; -} + }); + + return next(); +}; + +const setAppContext = (c: Context, context: AppContext) => { + c.set('context', context); +}; const isPageRequest = (request: Request) => { const url = new URL(request.url); diff --git a/apps/remix/server/index.ts b/apps/remix/server/index.ts index 421968034..a3a99f0ef 100644 --- a/apps/remix/server/index.ts +++ b/apps/remix/server/index.ts @@ -1,4 +1,5 @@ import { Hono } from 'hono'; +import { contextStorage } from 'hono/context-storage'; import { PDFDocument } from 'pdf-lib'; import { tsRestHonoApp } from '@documenso/api/hono'; @@ -11,10 +12,21 @@ import { putFile } from '@documenso/lib/universal/upload/put-file'; import { getPresignGetUrl } from '@documenso/lib/universal/upload/server-actions'; import { openApiDocument } from '@documenso/trpc/server/open-api'; +import { type AppContext, appContext } from './context'; import { openApiTrpcServerHandler } from './trpc/hono-trpc-open-api'; import { reactRouterTrpcServer } from './trpc/hono-trpc-remix'; -const app = new Hono(); +export interface HonoEnv { + Variables: { + context: AppContext; + }; +} + +const app = new Hono(); + +app.use(contextStorage()); + +app.use(appContext); // App middleware. // app.use('*', appMiddleware); diff --git a/apps/remix/server/trpc/hono-trpc-open-api.ts b/apps/remix/server/trpc/hono-trpc-open-api.ts index b5f3b1adc..95cd4c1c8 100644 --- a/apps/remix/server/trpc/hono-trpc-open-api.ts +++ b/apps/remix/server/trpc/hono-trpc-open-api.ts @@ -12,6 +12,7 @@ export const openApiTrpcServerHandler = async (c: Context) => { endpoint: API_V2_BETA_URL, router: appRouter, // Todo: Test this, since it's not using the createContext params. + // Todo: Reduce calls since we fetch on most request? maybe createContext: async () => createTrpcContext({ c, requestSource: 'apiV2' }), req: c.req.raw, onError: (opts) => handleTrpcRouterError(opts, 'apiV2'), diff --git a/apps/remix/server/utils/get-loader-session.ts b/apps/remix/server/utils/get-loader-session.ts index ad4345f8e..8a1fdc347 100644 --- a/apps/remix/server/utils/get-loader-session.ts +++ b/apps/remix/server/utils/get-loader-session.ts @@ -1,31 +1,43 @@ -import type { AppLoadContext } from 'react-router'; +import { getContext } from 'hono/context-storage'; import { redirect } from 'react-router'; +import type { HonoEnv } from 'server'; + +import type { AppSession } from '@documenso/lib/client-only/providers/session'; /** * Returns the session context or throws a redirect to signin if it is not present. */ -export const getRequiredLoaderSession = (context: AppLoadContext) => { - if (!context.session) { +export const getLoaderSession = (): AppSession => { + const session = getOptionalLoaderSession(); + + if (!session) { throw redirect('/signin'); // Todo: Maybe add a redirect cookie to come back? } + return session; +}; + +export const getOptionalLoaderSession = (): AppSession | null => { + const { context } = getContext().var; return context.session; }; /** * Returns the team session context or throws a redirect to signin if it is not present. */ -export const getRequiredLoaderTeamSession = (context: AppLoadContext) => { - if (!context.session) { +export const getLoaderTeamSession = () => { + const session = getOptionalLoaderSession(); + + if (!session) { throw redirect('/signin'); // Todo: Maybe add a redirect cookie to come back? } - if (!context.session.currentTeam) { + if (!session.currentTeam) { throw new Response(null, { status: 404 }); // Todo: Test that 404 page shows up. } return { - ...context.session, - currentTeam: context.session.currentTeam, + ...session, + currentTeam: session.currentTeam, }; }; diff --git a/apps/remix/vite.config.ts b/apps/remix/vite.config.ts index 01ece6b11..b5f6572c4 100644 --- a/apps/remix/vite.config.ts +++ b/apps/remix/vite.config.ts @@ -8,8 +8,6 @@ import { defineConfig, loadEnv } from 'vite'; import macrosPlugin from 'vite-plugin-babel-macros'; import tsconfigPaths from 'vite-tsconfig-paths'; -import { getLoadContext } from './server/load-context'; - export default defineConfig({ envDir: path.join(__dirname, '../../'), envPrefix: '__DO_NOT_USE_OR_YOU_WILL_BE_FIRED__', @@ -54,7 +52,6 @@ export default defineConfig({ lingui(), macrosPlugin(), serverAdapter({ - getLoadContext, entry: 'server/index.ts', }), tsconfigPaths(), diff --git a/packages/lib/client-only/providers/session.tsx b/packages/lib/client-only/providers/session.tsx index cc0539c2f..cb65bdaaa 100644 --- a/packages/lib/client-only/providers/session.tsx +++ b/packages/lib/client-only/providers/session.tsx @@ -1,25 +1,30 @@ import { createContext, useContext } from 'react'; import React from 'react'; -import type { Session, User } from '@prisma/client'; +import type { Session, User } from '@documenso/prisma/client'; -interface AuthProviderProps { - children: React.ReactNode; - session: DocumensoSession | null; -} +import type { TGetTeamByUrlResponse } from '../../server-only/team/get-team'; +import type { TGetTeamsResponse } from '../../server-only/team/get-teams'; -export type DocumensoSession = { - user: User; // Todo: Exclude password +export type AppSession = { session: Session; + user: User; // Todo: Remove password, and redundant fields. + currentTeam: TGetTeamByUrlResponse | null; + teams: TGetTeamsResponse; }; -const SessionContext = createContext(null); +interface SessionProviderProps { + children: React.ReactNode; + session: AppSession | null; +} + +const SessionContext = createContext(null); export const useSession = () => { const context = useContext(SessionContext); if (!context) { - throw new Error('useAuth must be used within a AuthProvider'); + throw new Error('useSession must be used within a SessionProvider'); } return context; @@ -34,6 +39,6 @@ export const useOptionalSession = () => { ); }; -export const SessionProvider = ({ children, session }: AuthProviderProps) => { +export const SessionProvider = ({ children, session }: SessionProviderProps) => { return {children}; };