Refactor session handling: implement useEnforcedSession for better session validation across components

This commit is contained in:
Marco Pedone 2025-08-20 14:04:23 +02:00
parent 1d70ce9a44
commit 485bcda0d3
11 changed files with 85 additions and 46 deletions

View file

@ -24,8 +24,13 @@ import { Sidebar } from "~/components/area-riservata/sidebar";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useSession } from "~/providers/SessionProvider"; import {
useSession,
EnforcedSessionContext,
} from "~/providers/SessionProvider";
import { WhatsAppIcon } from "~/components/svgs"; import { WhatsAppIcon } from "~/components/svgs";
import { LoadingPage } from "~/components/loading";
import type { ValidSession } from "~/server/api/trpc";
interface Props { interface Props {
children: ReactNode; children: ReactNode;
@ -66,6 +71,35 @@ export const AreaRiservataLayout = ({
}); });
const session = useSession(); const session = useSession();
if (session.status === "pending")
return (
<div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader />
<main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row">
<LoadingPage />;
</div>
</main>
{!noFooter && <MiniFooter />}
</div>
);
if (session.status !== "success" || !session.user) {
window.location.reload();
return (
<div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader />
<main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row">
<LoadingPage />;
</div>
</main>
{!noFooter && <MiniFooter />}
</div>
);
}
return ( return (
<div className="flex h-full min-h-screen flex-col text-clip"> <div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader /> <SiteHeader />
@ -77,9 +111,11 @@ export const AreaRiservataLayout = ({
<main className="flex h-full flex-1 overflow-auto"> <main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row"> <div className="flex h-auto w-full flex-col md:flex-row">
{noSidebar ? null : ( {noSidebar ? null : (
<Sidebar isAdmin={session.user?.isAdmin || false} /> <Sidebar isAdmin={session.user.isAdmin || false} />
)} )}
{children} <EnforcedSessionContext.Provider value={session as ValidSession}>
{children}
</EnforcedSessionContext.Provider>
</div> </div>
</main> </main>
{!noFooter && <MiniFooter />} {!noFooter && <MiniFooter />}

View file

@ -25,8 +25,12 @@ const AdminChats: NextPageWithLayout = () => {
isLoadingInactiveUsers isLoadingInactiveUsers
) )
return <LoadingPage />; return <LoadingPage />;
if (!session || !session.user || !activeChats || !inactiveUsers)
return <Status500 />; if (session.status !== "success" || !session.user) {
window.location.reload();
return <LoadingPage />;
}
if (!activeChats || !inactiveUsers) return <Status500 />;
return ( return (
<> <>

View file

@ -1,17 +1,12 @@
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { AreaRiservataLayout } from "~/components/Layout"; import { AreaRiservataLayout } from "~/components/Layout";
import Head from "next/head"; import Head from "next/head";
import { AllegatiComp } from "~/components/area-riservata/allegati"; import { AllegatiComp } from "~/components/area-riservata/allegati";
const Allegati: NextPageWithLayout = () => { const Allegati: NextPageWithLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const session = useSession(); const session = useEnforcedSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
return ( return (
<> <>

View file

@ -4,10 +4,9 @@ import Head from "next/head";
import { useState, type FormEvent } from "react"; import { useState, type FormEvent } from "react";
import { AreaRiservataLayout } from "~/components/Layout"; import { AreaRiservataLayout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading"; import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import type { StorageindexId } from "~/schemas/public/Storageindex"; import type { StorageindexId } from "~/schemas/public/Storageindex";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -37,7 +36,7 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
allegatoId, allegatoId,
}: AllegatoViewProps) => { }: AllegatoViewProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const session = useSession(); const session = useEnforcedSession();
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation(); const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({ const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
storageId: allegatoId, storageId: allegatoId,
@ -63,12 +62,8 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
}, },
}); });
if (session.status === "pending" || isLoading) return <LoadingPage />; if (isLoading) return <LoadingPage />;
if (!session || !session.user) {
console.error("Session not found or user not authenticated", session);
return <Status500 />;
}
if (!fileInfos) return <DocNotFoundPage />; if (!fileInfos) return <DocNotFoundPage />;
return ( return (

View file

@ -76,7 +76,7 @@ export const getServerSideProps = (async (context) => {
} }
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login?",
permanent: false, permanent: false,
}, },
}; };

View file

@ -1,18 +1,13 @@
import Head from "next/head"; import Head from "next/head";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import { AreaRiservataLayout } from "~/components/Layout"; import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page";
import { LoadingPage } from "~/components/loading";
import { EmailAccordion } from "~/components/area-riservata/comunicazioni"; import { EmailAccordion } from "~/components/area-riservata/comunicazioni";
const Comunicazioni: NextPageWithLayout = () => { const Comunicazioni: NextPageWithLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const session = useSession(); const session = useEnforcedSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
return ( return (
<> <>
@ -23,7 +18,7 @@ const Comunicazioni: NextPageWithLayout = () => {
<div className="mx-auto max-w-7xl grow p-4"> <div className="mx-auto max-w-7xl grow p-4">
<div className="p-1"> <div className="p-1">
<h1 className="text-2xl font-bold text-primary">Comunicazioni</h1> <h1 className="text-primary text-2xl font-bold">Comunicazioni</h1>
</div> </div>
<EmailAccordion userId={session.user.id} /> <EmailAccordion userId={session.user.id} />

View file

@ -1,10 +1,8 @@
import Head from "next/head"; import Head from "next/head";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import { AreaRiservataLayout } from "~/components/Layout"; import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page";
import { LoadingPage } from "~/components/loading";
import { import {
AdminDashboard, AdminDashboard,
UserDashboard, UserDashboard,
@ -12,10 +10,7 @@ import {
const Dashboard: NextPageWithLayout = () => { const Dashboard: NextPageWithLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const session = useSession(); const session = useEnforcedSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
return ( return (
<> <>

View file

@ -5,16 +5,13 @@ import Head from "next/head";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page"; import { Status500 } from "~/components/status-page";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import type { UsersId } from "~/schemas/public/Users"; import type { UsersId } from "~/schemas/public/Users";
import { OrdiniTable } from "~/components/tables/orders-table"; import { OrdiniTable } from "~/components/tables/orders-table";
const Ordini: NextPageWithLayout = () => { const Ordini: NextPageWithLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const session = useSession(); const session = useEnforcedSession();
if (session.status == "pending") return <LoadingPage />;
if (!session.user) return <Status500 />;
return ( return (
<> <>
<Head> <Head>

View file

@ -1,5 +1,4 @@
import Head from "next/head"; import Head from "next/head";
import { LoadingPage } from "~/components/loading";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { AreaRiservataLayout } from "~/components/Layout"; import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app"; import type { NextPageWithLayout } from "~/pages/_app";
@ -9,7 +8,7 @@ import { Status500 } from "~/components/status-page";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { ProfileFormAccount } from "~/forms/FormProfilo_Account"; import { ProfileFormAccount } from "~/forms/FormProfilo_Account";
import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica"; import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica";
import { useSession } from "~/providers/SessionProvider"; import { useEnforcedSession } from "~/providers/SessionProvider";
import { import {
Card, Card,
CardContent, CardContent,
@ -18,15 +17,16 @@ import {
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { UserAvatar } from "~/components/user_avatar"; import { UserAvatar } from "~/components/user_avatar";
import { LoadingPage } from "~/components/loading";
const Dashboard: NextPageWithLayout = () => { const Dashboard: NextPageWithLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: userData, isLoading } = api.users.getClientProfilo.useQuery({ const { data: userData, isLoading } = api.users.getClientProfilo.useQuery({
id: undefined, id: undefined,
}); });
const session = useSession(); const session = useEnforcedSession();
if (isLoading) return <LoadingPage />; if (isLoading) return <LoadingPage />;
if (!userData || !session.user) return <Status500 />; if (!userData) return <Status500 />;
return ( return (
<> <>

View file

@ -6,7 +6,7 @@ import {
type FC, type FC,
type ReactNode, type ReactNode,
} from "react"; } from "react";
import type { Session } from "~/server/api/trpc"; import type { Session, ValidSession } from "~/server/api/trpc";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
type SessionStatus = "pending" | "success" | "error"; type SessionStatus = "pending" | "success" | "error";
export type SessionContextType = { export type SessionContextType = {
@ -66,3 +66,20 @@ export const SessionProvider: FC<{ children: ReactNode }> = ({ children }) => {
}; };
export const useSession = () => useContext(SessionContext); export const useSession = () => useContext(SessionContext);
export const EnforcedSessionContext = createContext<ValidSession | undefined>(
undefined,
);
/**
* AreaRiservata specific session context. It ensures that the session is valid.
*/
export const useEnforcedSession = () => {
const session = useContext(EnforcedSessionContext);
if (!session) {
throw new Error(
"useValidSession must be used within a ValidSessionProvider",
);
}
return session;
};

View file

@ -36,6 +36,11 @@ export const sessionSchema = z.object({
export type Session = z.infer<typeof sessionSchema>; export type Session = z.infer<typeof sessionSchema>;
export type ValidSession = {
user: Session;
status: "success";
};
interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> { interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> {
session: Session | null; session: Session | null;
res?: NextApiResponse; res?: NextApiResponse;