From 6d004be2290f6bb76805101923e21ebc58f834a3 Mon Sep 17 00:00:00 2001 From: cesarLima1 <105736261+cesarLima1@users.noreply.github.com> Date: Tue, 19 Nov 2024 18:01:40 -0400 Subject: [PATCH] =?UTF-8?q?[TM-1489]=20Update=20middleware=20to=20handle?= =?UTF-8?q?=20dashboard=20access=20for=20all=20user=20t=E2=80=A6=20(#687)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [TM-1489] Update middleware to handle dashboard access for all user types and roles * [TM-1489] use cache to pass test * [TM-1489] use cache to pass test * [TM-1489] add next response in middleware --- src/middleware.page.ts | 188 ++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 97 deletions(-) diff --git a/src/middleware.page.ts b/src/middleware.page.ts index 7406e858c..3c090d938 100644 --- a/src/middleware.page.ts +++ b/src/middleware.page.ts @@ -14,114 +14,108 @@ export async function middleware(request: NextRequest) { const accessToken = request.cookies.get("accessToken")?.value; const middlewareCache = request.cookies.get(MiddlewareCacheKey)?.value; + // Allow unauthenticated access to dashboard routes if (request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.next(); } - if (!!accessToken && !!middlewareCache) { - // Skip middleware for dashboard routes to prevent redirect loop + if (accessToken && middlewareCache) { + // Only redirect for specific routes when cached middleware exists + const shouldRedirect = request.nextUrl.pathname === "/" || request.nextUrl.pathname.startsWith("/auth"); + + if (shouldRedirect) { + matcher.redirect(middlewareCache); + return matcher.getResult(); + } + return NextResponse.next(); + } + + if (!accessToken) { + matcher.startWith("/home")?.redirect("/"); + matcher.startWith("/auth")?.next(); + matcher.exact("/")?.next(); + + if (!matcher.hasMatch()) { + matcher.redirect("/auth/login"); + } + return matcher.getResult(); + } + + // The redux store isn't available yet at this point, so we do a quick manual users/me fetch + // to get the data we need to resolve routing. + const result = await fetch(resolveUrl("/users/v3/users/me"), { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}` + } + }); + const json = await result.json(); + + const user = json.data.attributes as UserDto; + const { + id: organisationId, + meta: { userStatus } + } = json.data.relationships?.org?.data ?? { meta: {} }; + const organisation: OrganisationDto | undefined = json.included?.[0]?.attributes; + + // Early return for unverified email + if (!user?.emailAddressVerifiedAt) { + matcher.redirect(`/auth/signup/confirm?email=${user?.emailAddress}`); + return matcher.getResult(); + } + + const userIsAdmin = isAdmin(user?.primaryRole as UserRole); + const userIsFunderOrGovernment = user?.primaryRole === "funder" || user?.primaryRole === "government"; + + // Always handle funder/government users first + if (userIsFunderOrGovernment) { + if (!request.nextUrl.pathname.startsWith("/dashboard")) { + return NextResponse.redirect(new URL("/dashboard/learn-more?tab=about-us", request.url)); + } + return NextResponse.next(); + } + + // Handle admin users + if (userIsAdmin) { + // Allow admins to access dashboard routes if (request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.next(); } - //If middleware result is cached bypass api call to improve performance - matcher.when(middlewareCache.includes("admin"))?.redirect(middlewareCache); - matcher.exact("/")?.redirect(middlewareCache); - matcher.startWith("/auth")?.redirect(middlewareCache); - matcher.next(); + // Default admin redirect for non-dashboard routes + matcher.redirect(`/admin`, { cacheResponse: true }); return matcher.getResult(); } - await matcher.if( - !accessToken, - async () => { - //Not logged-in - - matcher.startWith("/home")?.redirect("/"); - matcher.startWith("/auth")?.next(); - matcher.exact("/")?.next(); - - if (!matcher.hasMatch()) { - matcher.redirect("/auth/login"); - } - }, - async () => { - // The redux store isn't available yet at this point, so we do a quick manual users/me fetch - // to get the data we need to resolve routing. - const result = await fetch(resolveUrl("/users/v3/users/me"), { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}` - } - }); - const json = await result.json(); - - const user = json.data.attributes as UserDto; - const { - id: organisationId, - meta: { userStatus } - } = json.data.relationships?.org?.data ?? { meta: {} }; - const organisation: OrganisationDto | undefined = json.included?.[0]?.attributes; - - matcher.if( - !user?.emailAddressVerifiedAt, - () => { - //Email is not verified - matcher.redirect(`/auth/signup/confirm?email=${user?.emailAddress}`); - }, - () => { - //Email is verified - const userIsAdmin = isAdmin(user?.primaryRole as UserRole); - const userIsFunderOrGovernment = user?.primaryRole === "funder" || user?.primaryRole === "government"; - - // Redirect funder/government users to /dashboard/learn-more?tab=about-us - if (userIsFunderOrGovernment) { - matcher.redirect(`/dashboard/learn-more?tab=about-us`, { cacheResponse: true }); - return matcher.getResult(); - } - - // Allow admin users to access dashboard routes - if (userIsAdmin && request.nextUrl.pathname.startsWith("/dashboard")) { - matcher.next(); - return matcher.getResult(); - } - - // Default admin redirect for non-dashboard routes - matcher.when(user != null && userIsAdmin)?.redirect(`/admin`, { cacheResponse: true }); - - matcher - .when(organisation != null && organisation.status !== "draft") - ?.startWith("/organization/create") - ?.redirect(`/organization/create/confirm`); - - matcher.when(organisation == null)?.redirect(`/organization/assign`); - - matcher.when(organisation?.status === "draft")?.redirect(`/organization/create`); - - matcher.when(userStatus === "requested")?.redirect(`/organization/status/pending`); - - matcher - .when(organisationId != null) - ?.exact("/organization") - ?.redirect(`/organization/${organisationId}`); - - matcher.when(organisation?.status === "rejected")?.redirect(`/organization/status/rejected`); - - matcher.exact("/")?.redirect(`/home`); - - matcher.startWith("/auth")?.redirect("/home"); - - if (!userIsAdmin && organisation?.status === "approved" && userStatus !== "requested") { - //Cache result if user has and approved org - matcher.next().cache("/home"); - } else { - matcher.next(); - } - } - ); - } - ); + matcher + .when(organisation != null && organisation.status !== "draft") + ?.startWith("/organization/create") + ?.redirect(`/organization/create/confirm`); + + matcher.when(organisation == null)?.redirect(`/organization/assign`); + + matcher.when(organisation?.status === "draft")?.redirect(`/organization/create`); + + matcher.when(userStatus === "requested")?.redirect(`/organization/status/pending`); + + matcher + .when(organisationId != null) + ?.exact("/organization") + ?.redirect(`/organization/${organisationId}`); + + matcher.when(organisation?.status === "rejected")?.redirect(`/organization/status/rejected`); + + matcher.exact("/")?.redirect(`/home`); + + matcher.startWith("/auth")?.redirect("/home"); + + if (organisation?.status === "approved" && userStatus !== "requested") { + // Cache result if user has an approved org + matcher.next().cache("/home"); + } else { + matcher.next(); + } } catch (error) { Sentry.captureException(error); matcher.redirect("/"); //To be redirected to a custom error page