From c2ae6964f9b4192e6b0358205e92ddb18f0662bd Mon Sep 17 00:00:00 2001 From: Robert Bond Date: Thu, 14 May 2026 10:38:45 +0000 Subject: [PATCH] Merged PR 2318: auth stabilisation: extract shared myportal auth guard helper auth stabilisation: extract shared myportal auth guard helper ntroduces a small shared SSR helper (resolveMyPortalAuthContext) to standardize common myportal auth/session guards (session presence, session user identity, and pinsUser cookie) with preserved reason-coded diagnostics and signin redirect behavior. Migrates exactly two loaders (pages/myportal/searchresults.js, pages/myportal/addresssearchresults.js) to use the helper while keeping loader-specific UPSTREAM_FAILURE and CONTACT_LOOKUP_FAILED logic unchanged. Related work items: #23020 --- lib/auth/resolveMyPortalAuthContext.js | 58 ++++++ pages/myportal/addresssearchresults.js | 167 +++++++----------- pages/myportal/searchresults.js | 156 +++++++--------- ...ddresssearchresults-loader-guards.test.cjs | 22 ++- ...ortal-searchresults-loader-guards.test.cjs | 24 ++- 5 files changed, 224 insertions(+), 203 deletions(-) create mode 100644 lib/auth/resolveMyPortalAuthContext.js diff --git a/lib/auth/resolveMyPortalAuthContext.js b/lib/auth/resolveMyPortalAuthContext.js new file mode 100644 index 00000000..2d16973d --- /dev/null +++ b/lib/auth/resolveMyPortalAuthContext.js @@ -0,0 +1,58 @@ +import { getSession } from "next-auth/react"; +import { consoleLogger } from "../../actions/core/logger"; + +const signinRedirect = { + destination: "/auth/signin", + permanent: false +}; + +const buildSigninRedirectResult = () => ({ redirect: signinRedirect }); + +export async function resolveMyPortalAuthContext(ctx, options = {}) { + const { + loggerName = "MyPortalAuthGuard", + noSessionMessage = "myportal loader missing session; redirecting to signin", + noSessionUserMessage = "myportal loader missing session user identity; redirecting to signin", + noPinsUserMessage = "myportal loader missing pinsUser cookie; redirecting to signin" + } = options; + + const session = await getSession(ctx); + + if (!session) { + consoleLogger({ + name: loggerName, + reasonCode: "NO_SESSION", + message: noSessionMessage + }); + return { ok: false, redirect: buildSigninRedirectResult().redirect }; + } + + if (!session?.user?.id || !session?.user?.email) { + consoleLogger({ + name: loggerName, + reasonCode: "NO_SESSION_USER", + message: noSessionUserMessage, + hasSessionUserId: !!session?.user?.id, + hasSessionUserEmail: !!session?.user?.email + }); + return { ok: false, redirect: buildSigninRedirectResult().redirect }; + } + + const pinsUser = ctx?.req?.cookies?.pinsUser; + if (!pinsUser) { + consoleLogger({ + name: loggerName, + reasonCode: "NO_PINSUSER_COOKIE", + message: noPinsUserMessage + }); + return { ok: false, redirect: buildSigninRedirectResult().redirect }; + } + + return { + ok: true, + session, + sessionUserId: session.user.id, + sessionUserEmail: session.user.email, + pinsUser + }; +} diff --git a/pages/myportal/addresssearchresults.js b/pages/myportal/addresssearchresults.js index 96b643f8..608af39f 100644 --- a/pages/myportal/addresssearchresults.js +++ b/pages/myportal/addresssearchresults.js @@ -1,4 +1,3 @@ -import { getSession } from "next-auth/react"; import useTranslation from "next-translate/useTranslation"; import Head from "next/head"; import { useRouter } from "next/router"; @@ -39,6 +38,7 @@ import { import { wrapper } from "../../store/store"; import ServiceBanner from "../../components/myportal/servicebanner"; import TimeOut from "../../components/timeout"; +import { resolveMyPortalAuthContext } from "../../lib/auth/resolveMyPortalAuthContext"; const Home = (props) => { const { footerLinks, pages } = props; @@ -147,17 +147,51 @@ export const getServerSideProps = wrapper.getServerSideProps( store.dispatch(setShowReps(showReps, showLoginCheck)); store.dispatch(setSearch(Object.entries(query))); - const { cookies } = req; + const authContext = await resolveMyPortalAuthContext(ctx, { + loggerName: "MyPortalAddressSearchResultsAuthGuard", + noSessionMessage: + "myportal addresssearchresults loader missing session; redirecting to signin", + noSessionUserMessage: + "myportal addresssearchresults loader missing session user identity; redirecting to signin", + noPinsUserMessage: + "myportal addresssearchresults loader missing pinsUser cookie; redirecting to signin" + }); - let loggedInUser = cookies.pinsUser; - let thisSession = await getSession(ctx); + if (!authContext.ok) { + return { redirect: authContext.redirect }; + } - if (!thisSession) { + const thisSession = authContext.session; + const loggedInUser = authContext.pinsUser; + + thisSession != false && + store.dispatch(setContainerID(thisSession.user.id)); + + let accountDetails; + let searchResultsObj; + let watchedCases; + let searchDetailsObj; + let watchedCasesDetails; + + try { + [accountDetails, searchResultsObj, watchedCases] = + await Promise.all([ + getPersonalAccount(loggedInUser), + getAdvancedSearch(query), + getWatchedCases(loggedInUser) + ]); + + [searchDetailsObj, watchedCasesDetails] = await Promise.all([ + getSearchDetails(searchResultsObj), + getDetails(watchedCases, "myWatchedCases") + ]); + } catch (error) { consoleLogger({ name: "MyPortalAddressSearchResultsAuthGuard", - reasonCode: "NO_SESSION", + reasonCode: "UPSTREAM_FAILURE", message: - "myportal addresssearchresults loader missing session; redirecting to signin" + "myportal addresssearchresults loader dependency call failed; redirecting to signin", + error: error?.message }); return { redirect: { @@ -165,102 +199,33 @@ export const getServerSideProps = wrapper.getServerSideProps( permanent: false } }; - } else { - if (!thisSession?.user?.id || !thisSession?.user?.email) { - consoleLogger({ - name: "MyPortalAddressSearchResultsAuthGuard", - reasonCode: "NO_SESSION_USER", - message: - "myportal addresssearchresults loader missing session user identity; redirecting to signin", - hasSessionUserId: !!thisSession?.user?.id, - hasSessionUserEmail: !!thisSession?.user?.email - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - if (!loggedInUser) { - consoleLogger({ - name: "MyPortalAddressSearchResultsAuthGuard", - reasonCode: "NO_PINSUSER_COOKIE", - message: - "myportal addresssearchresults loader missing pinsUser cookie; redirecting to signin" - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - thisSession != false && - store.dispatch(setContainerID(thisSession.user.id)); - - let accountDetails; - let searchResultsObj; - let watchedCases; - let searchDetailsObj; - let watchedCasesDetails; - - try { - [accountDetails, searchResultsObj, watchedCases] = - await Promise.all([ - getPersonalAccount(loggedInUser), - getAdvancedSearch(query), - getWatchedCases(loggedInUser) - ]); - - [searchDetailsObj, watchedCasesDetails] = await Promise.all([ - getSearchDetails(searchResultsObj), - getDetails(watchedCases, "myWatchedCases") - ]); - } catch (error) { - consoleLogger({ - name: "MyPortalAddressSearchResultsAuthGuard", - reasonCode: "UPSTREAM_FAILURE", - message: - "myportal addresssearchresults loader dependency call failed; redirecting to signin", - error: error?.message - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - if (!accountDetails || accountDetails?.errorCode) { - consoleLogger({ - name: "MyPortalAddressSearchResultsAuthGuard", - reasonCode: "CONTACT_LOOKUP_FAILED", - message: - "myportal addresssearchresults loader account lookup failed; redirecting to signin", - hasAccountDetails: !!accountDetails, - hasErrorCode: !!accountDetails?.errorCode - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - store.dispatch(setAccountDetails(accountDetails)); - store.dispatch(setSearchResults(searchResultsObj)); - store.dispatch(setSearchDetails(searchDetailsObj)); - store.dispatch(setWatchedCases(watchedCases)); - store.dispatch(setWatchedCasesDetails(watchedCasesDetails)); - store.dispatch(setSearch(Object.entries(query))); - store.dispatch(setLoggedInUserId(loggedInUser)); } + if (!accountDetails || accountDetails?.errorCode) { + consoleLogger({ + name: "MyPortalAddressSearchResultsAuthGuard", + reasonCode: "CONTACT_LOOKUP_FAILED", + message: + "myportal addresssearchresults loader account lookup failed; redirecting to signin", + hasAccountDetails: !!accountDetails, + hasErrorCode: !!accountDetails?.errorCode + }); + return { + redirect: { + destination: "/auth/signin", + permanent: false + } + }; + } + + store.dispatch(setAccountDetails(accountDetails)); + store.dispatch(setSearchResults(searchResultsObj)); + store.dispatch(setSearchDetails(searchDetailsObj)); + store.dispatch(setWatchedCases(watchedCases)); + store.dispatch(setWatchedCasesDetails(watchedCasesDetails)); + store.dispatch(setSearch(Object.entries(query))); + store.dispatch(setLoggedInUserId(loggedInUser)); + return { props: { searchResultsObj: { diff --git a/pages/myportal/searchresults.js b/pages/myportal/searchresults.js index dcc2e244..042630b7 100644 --- a/pages/myportal/searchresults.js +++ b/pages/myportal/searchresults.js @@ -31,8 +31,8 @@ import { setWatchedCasesDetails } from "../../store/watchedCases/action"; -import { getSession } from "next-auth/react"; import TimeOut from "../../components/timeout"; +import { resolveMyPortalAuthContext } from "../../lib/auth/resolveMyPortalAuthContext"; import { setAccountDetails, setContainerID, @@ -81,20 +81,37 @@ export const getServerSideProps = wrapper.getServerSideProps( getIP(req); console.log("query-", query); - const { cookies } = req; - - let thisSession = await getSession(ctx); - const showReps = process.env.SHOWREPRESENTATIONS || false; const showLoginCheck = process.env.SHOWLOGIN || false; store.dispatch(setShowReps(showReps, showLoginCheck)); - if (!thisSession) { + const authContext = await resolveMyPortalAuthContext(ctx, { + loggerName: "MyPortalSearchResultsAuthGuard", + noSessionMessage: + "myportal searchresults loader missing session; redirecting to signin", + noSessionUserMessage: + "myportal searchresults loader missing session user identity; redirecting to signin", + noPinsUserMessage: + "myportal searchresults loader missing pinsUser cookie; redirecting to signin" + }); + + if (!authContext.ok) { + return { redirect: authContext.redirect }; + } + + const thisSession = authContext.session; + + let loggedInUser; + try { + const portalLogin = await getPortalLogin(thisSession.user.email); + loggedInUser = portalLogin?.value?.[0]?.contactid; + } catch (error) { consoleLogger({ name: "MyPortalSearchResultsAuthGuard", - reasonCode: "NO_SESSION", + reasonCode: "UPSTREAM_FAILURE", message: - "myportal searchresults loader missing session; redirecting to signin" + "myportal searchresults loader portal login lookup failed; redirecting to signin", + error: error?.message }); return { redirect: { @@ -102,95 +119,42 @@ export const getServerSideProps = wrapper.getServerSideProps( permanent: false } }; - } else { - if (!thisSession?.user?.id || !thisSession?.user?.email) { - consoleLogger({ - name: "MyPortalSearchResultsAuthGuard", - reasonCode: "NO_SESSION_USER", - message: - "myportal searchresults loader missing session user identity; redirecting to signin", - hasSessionUserId: !!thisSession?.user?.id, - hasSessionUserEmail: !!thisSession?.user?.email - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - if (!cookies?.pinsUser) { - consoleLogger({ - name: "MyPortalSearchResultsAuthGuard", - reasonCode: "NO_PINSUSER_COOKIE", - message: - "myportal searchresults loader missing pinsUser cookie; redirecting to signin" - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - let loggedInUser; - try { - const portalLogin = await getPortalLogin( - thisSession.user.email - ); - loggedInUser = portalLogin?.value?.[0]?.contactid; - } catch (error) { - consoleLogger({ - name: "MyPortalSearchResultsAuthGuard", - reasonCode: "UPSTREAM_FAILURE", - message: - "myportal searchresults loader portal login lookup failed; redirecting to signin", - error: error?.message - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - if (!loggedInUser) { - consoleLogger({ - name: "MyPortalSearchResultsAuthGuard", - reasonCode: "CONTACT_LOOKUP_FAILED", - message: - "myportal searchresults loader missing CRM contact id; redirecting to signin" - }); - return { - redirect: { - destination: "/auth/signin", - permanent: false - } - }; - } - - //console.log("sssss", searchResultsObj); - - const watchedCases = await getWatchedCases(loggedInUser); - - //console.log("sssss", loggedInUser); - - const [accountDetails, watchedCasesDetails] = await Promise.all([ - getPersonalAccount(loggedInUser), - getDetails(watchedCases, "myWatchedCases") - ]); - store.dispatch(setAccountDetails(accountDetails)); - store.dispatch(setSearch(query.q)); - store.dispatch(setWatchedCases(watchedCases)); - store.dispatch(setWatchedCasesDetails(watchedCasesDetails)); - store.dispatch(setLoggedInUserId(loggedInUser)); - - thisSession != false && - store.dispatch(setContainerID(thisSession.user.id)); } + + if (!loggedInUser) { + consoleLogger({ + name: "MyPortalSearchResultsAuthGuard", + reasonCode: "CONTACT_LOOKUP_FAILED", + message: + "myportal searchresults loader missing CRM contact id; redirecting to signin" + }); + return { + redirect: { + destination: "/auth/signin", + permanent: false + } + }; + } + + //console.log("sssss", searchResultsObj); + + const watchedCases = await getWatchedCases(loggedInUser); + + //console.log("sssss", loggedInUser); + + const [accountDetails, watchedCasesDetails] = await Promise.all([ + getPersonalAccount(loggedInUser), + getDetails(watchedCases, "myWatchedCases") + ]); + store.dispatch(setAccountDetails(accountDetails)); + store.dispatch(setSearch(query.q)); + store.dispatch(setWatchedCases(watchedCases)); + store.dispatch(setWatchedCasesDetails(watchedCasesDetails)); + store.dispatch(setLoggedInUserId(loggedInUser)); + + thisSession != false && + store.dispatch(setContainerID(thisSession.user.id)); + return { props: { showLoginCheck: showLoginCheck diff --git a/tests/phase22/myportal-addresssearchresults-loader-guards.test.cjs b/tests/phase22/myportal-addresssearchresults-loader-guards.test.cjs index 4db5f6ee..c137ee02 100644 --- a/tests/phase22/myportal-addresssearchresults-loader-guards.test.cjs +++ b/tests/phase22/myportal-addresssearchresults-loader-guards.test.cjs @@ -48,6 +48,11 @@ const loadMyPortalAddressSearchResultsPage = (overrides = {}) => { setWatchedCases: () => ({}), setWatchedCasesDetails: () => ({}), setLoggedInUserId: () => ({}), + resolveMyPortalAuthContext: async (ctx) => ({ + ok: true, + session: { user: { id: "u-1", email: "x@y.z" } }, + pinsUser: ctx?.req?.cookies?.pinsUser + }), wrapper: { getServerSideProps: (factory) => async (ctx) => { const store = { dispatch: () => {} }; @@ -65,7 +70,10 @@ const loadMyPortalAddressSearchResultsPage = (overrides = {}) => { test("addresssearchresults redirects to signin when session is missing", async () => { const mod = loadMyPortalAddressSearchResultsPage({ - getSession: async () => null + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, @@ -76,7 +84,10 @@ test("addresssearchresults redirects to signin when session is missing", async ( test("addresssearchresults redirects to signin when session user identity is missing", async () => { const mod = loadMyPortalAddressSearchResultsPage({ - getSession: async () => ({ user: { id: "u-1" } }) + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, @@ -86,7 +97,12 @@ test("addresssearchresults redirects to signin when session user identity is mis }); test("addresssearchresults redirects to signin when pinsUser cookie is missing", async () => { - const mod = loadMyPortalAddressSearchResultsPage(); + const mod = loadMyPortalAddressSearchResultsPage({ + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) + }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, req: { cookies: {} } diff --git a/tests/phase22/myportal-searchresults-loader-guards.test.cjs b/tests/phase22/myportal-searchresults-loader-guards.test.cjs index 79a8c598..08875a0f 100644 --- a/tests/phase22/myportal-searchresults-loader-guards.test.cjs +++ b/tests/phase22/myportal-searchresults-loader-guards.test.cjs @@ -46,6 +46,11 @@ const loadMyPortalSearchResultsPage = (overrides = {}) => { setWatchedCasesDetails: () => ({}), setLoggedInUserId: () => ({}), setContainerID: () => ({}), + resolveMyPortalAuthContext: async (ctx) => ({ + ok: true, + session: { user: { id: "u-1", email: "test@example.com" } }, + pinsUser: ctx?.req?.cookies?.pinsUser + }), wrapper: { getServerSideProps: (factory) => async (ctx) => { const store = { dispatch: () => {} }; @@ -62,7 +67,12 @@ const loadMyPortalSearchResultsPage = (overrides = {}) => { }; test("myportal searchresults redirects to signin when session is missing", async () => { - const mod = loadMyPortalSearchResultsPage({ getSession: async () => null }); + const mod = loadMyPortalSearchResultsPage({ + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) + }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, req: { cookies: { pinsUser: "contact-1" } } @@ -72,7 +82,10 @@ test("myportal searchresults redirects to signin when session is missing", async test("myportal searchresults redirects to signin when session user identity is missing", async () => { const mod = loadMyPortalSearchResultsPage({ - getSession: async () => ({ user: { id: "u-1" } }) + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, @@ -82,7 +95,12 @@ test("myportal searchresults redirects to signin when session user identity is m }); test("myportal searchresults redirects to signin when pinsUser cookie is missing", async () => { - const mod = loadMyPortalSearchResultsPage(); + const mod = loadMyPortalSearchResultsPage({ + resolveMyPortalAuthContext: async () => ({ + ok: false, + redirect: { destination: "/auth/signin", permanent: false } + }) + }); const result = await mod.getServerSideProps({ query: { q: "CAS" }, req: { cookies: {} }