Skip to content

Commit

Permalink
[TM-1489] Update middleware to handle dashboard access for all user t… (
Browse files Browse the repository at this point in the history
#687)

* [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
  • Loading branch information
cesarLima1 authored Nov 19, 2024
1 parent 1f9bab3 commit 6d004be
Showing 1 changed file with 91 additions and 97 deletions.
188 changes: 91 additions & 97 deletions src/middleware.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 6d004be

Please sign in to comment.