import { graphql } from "@/lib/gql";
import { middleware as i18nMiddleware, redirect } from "@/lib/i18n";
import gqlRequest from "graphql-request";
import type { NextRequest } from "next/server";
import { pathToRegexp } from "path-to-regexp";
import type { Address } from "viem";
import { auth } from "./lib/auth";
const precomputePathRegex = (patterns: (RegExp | string)[]) => {
return patterns.map((pattern) => (pattern instanceof RegExp ? pattern : pathToRegexp(pattern)));
};
const buildRouteMatcher = (routes: (RegExp | string)[]) => {
const matchers = precomputePathRegex(routes);
return (request: NextRequest) => matchers.some((matcher) => matcher.test(request.nextUrl.pathname));
};
const isAuthRoute = buildRouteMatcher(["/auth", "/auth/(.*)"]);
const isWeb3AuthRoute = buildRouteMatcher(["/auth/web3", "/auth/web3/(.*)"]);
const isPolygonIdAuthRoute = buildRouteMatcher(["/auth/polygonid", "/auth/polygonid/(.*)"]);
const isPolygonIdConnectAuthRoute = buildRouteMatcher(["/auth/polygonid/connect"]);
const isPolygonIdRegisterAuthRoute = buildRouteMatcher(["/auth/polygonid/register"]);
const isPolygonIdHoldAuthRoute = buildRouteMatcher(["/auth/polygonid/hold"]);
const isPolygonIdRequestAuthRoute = buildRouteMatcher(["/auth/polygonid/request"]);
const CheckForVerifiedCredential = graphql(/* GraphQL */ `
query CheckForVerifiedCredential($address: String!) {
blockchain_address_by_pk(address: $address) {
credentials_aggregate {
aggregate {
count
}
}
}
}
`);
const hasVerifiedCredential = async (address: Address) => {
const response = await gqlRequest(
process.env.HASURA_GRAPHQL || "",
CheckForVerifiedCredential,
{
address,
},
{
"x-hasura-admin-secret": process.env.HASURA_SECRET || "",
},
);
return (response.blockchain_address_by_pk?.credentials_aggregate.aggregate?.count ?? 0) > 0;
};
const CheckExistingRegistration = graphql(/* GraphQL */ `
query CheckExistingRegistration($did: String!, $address: String!){
customer_registration_aggregate(where: {did: {_eq: $did}, address: {_eq: $address}}) {
aggregate {
count
}
}
}
`);
const hasExistingRegistration = async (address: Address, did: string) => {
const response = await gqlRequest(
process.env.HASURA_GRAPHQL || "",
CheckExistingRegistration,
{
address,
did,
},
{
"x-hasura-admin-secret": process.env.HASURA_SECRET || "",
},
);
return (response?.customer_registration_aggregate?.aggregate?.count ?? 0) > 0;
};
// TODO
// const CheckIssuedCredential = graphql(/* GraphQL */ `
// query CheckExistingRegistration($did: String!, $address: String!){
// customer_registration_aggregate(where: {did: {_eq: $did}, address: {_eq: $address}}) {
// aggregate {
// count
// }
// }
// }
// `);
// TODO
const hasIssuedCredential = async (did: string) => {
// const response = await gqlRequest(
// process.env.HASURA_GRAPHQL || "",
// CheckExistingRegistration,
// {
// address,
// did,
// },
// {
// "x-hasura-admin-secret": process.env.HASURA_SECRET || "",
// },
// );
// return (response?.customer_registration_aggregate?.aggregate?.count ?? 0) > 0;
return false;
};
export async function middleware(request: NextRequest) {
const session = await auth();
if (isAuthRoute(request)) {
if (isWeb3AuthRoute(request)) {
// we are on the web3 route, we only need to be here if we are not connected yet
// we can get this from the session, if the address is set.
if (session?.user.address) {
if (await hasVerifiedCredential(session.user.address)) {
// since we already did the KYC, the user can go to the customer section
return redirect("/customer");
}
return redirect("/auth/polygonid");
}
} else {
// this check is actually extra, but for clarity sake i want to be explicit
if (isPolygonIdAuthRoute(request)) {
// first, if address is not set, we should not be here at all
if (!session?.user.address) {
return redirect("/auth/web3");
}
// we are only need to be in this section if we have not kyced the user yet
if (await hasVerifiedCredential(session.user.address)) {
// since we already did the KYC, the user can go to the customer section
return redirect("/customer");
}
if (isPolygonIdConnectAuthRoute(request)) {
// if we have a did in the session, we are already connected in this section
if (session?.user.did) {
if (await hasIssuedCredential(session.user.did)) {
return redirect("/auth/polygonid/request");
}
if (await hasExistingRegistration(session.user.address, session.user.did)) {
// since we already did the KYC, the user can go to the customer section
return redirect("/auth/polygonid/hold");
}
// no issued credential, and no registration, go to registration
return redirect("/auth/polygonid/request");
}
} else {
// the connect page handles redirection on its own, so we would end up on the register or request pages
if (isPolygonIdRequestAuthRoute(request)) {
// TODO
} else {
// we need a did to register or hold
if (!session?.user.did) {
return redirect("/auth/polygonid/connect");
}
if (isPolygonIdRegisterAuthRoute(request)) {
if (await hasExistingRegistration(session.user.address, session.user.did)) {
// if we are already registered, we can go to the hold page
return redirect("/auth/polygonid/hold");
}
} else {
if (isPolygonIdHoldAuthRoute(request)) {
if (!(await hasExistingRegistration(session.user.address, session.user.did))) {
// we cannot be on the hold page if we are not registered
return redirect("/auth/polygonid/register");
}
}
}
}
}
// final option if we did not match anything else
return redirect("/auth/polygonid/connect");
}
}
// final option if we are in the root of auth
return redirect("/auth/web3");
}
return i18nMiddleware(request);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
};
This is the middleware