@tradecrush/next-route-guard
Version:
Convention-based route authentication middleware for Next.js applications
247 lines (245 loc) • 8.54 kB
JavaScript
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/route-guard.ts
import { NextResponse } from "next/server";
var DEFAULT_LOGIN_ROUTE = "/login";
function createRouteGuardMiddleware(options) {
const {
isAuthenticated,
onUnauthenticated = (request) => {
const url = request.nextUrl.clone();
url.pathname = DEFAULT_LOGIN_ROUTE;
url.searchParams.set("from", request.nextUrl.pathname);
return NextResponse.redirect(url);
},
routeMap,
defaultProtected = true,
excludeUrls = ["/api/(.*)"]
} = options;
const routeTrie = buildRouteTrie(routeMap);
return function routeGuardMiddleware(request) {
return __async(this, null, function* () {
const pathname = request.nextUrl.pathname;
for (const pattern of excludeUrls) {
const isRegex = pattern instanceof RegExp;
const regex = isRegex ? pattern : new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
if (regex.test(pathname)) {
return NextResponse.next();
}
}
const isProtected = matchPath(pathname, routeTrie, defaultProtected);
if (!isProtected) {
return NextResponse.next();
}
const isAuthed = yield isAuthenticated(request);
if (isAuthed) {
return NextResponse.next();
}
return onUnauthenticated(request);
});
};
}
function buildRouteTrie(routeMap) {
const root = {
children: /* @__PURE__ */ new Map()
};
for (const route of routeMap.protected) {
addRouteToTrie(root, route, true);
}
for (const route of routeMap.public) {
addRouteToTrie(root, route, false);
}
return root;
}
function addRouteToTrie(root, route, isProtected) {
const segments = route.split("/").filter((segment) => segment !== "");
let current = root;
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (segment.startsWith("[...") || segment.startsWith("[[...")) {
const isOptional = segment.startsWith("[[...");
if (!current.catchAllChild) {
current.catchAllChild = {
node: { children: /* @__PURE__ */ new Map() },
isOptional
};
}
if (i === segments.length - 1) {
current.catchAllChild.node.isProtected = isProtected;
}
current = current.catchAllChild.node;
} else if (segment.startsWith("[") && segment.endsWith("]")) {
if (!current.dynamicChild) {
current.dynamicChild = { children: /* @__PURE__ */ new Map() };
}
if (i === segments.length - 1) {
current.dynamicChild.isProtected = isProtected;
}
current = current.dynamicChild;
} else {
if (!current.children.has(segment)) {
current.children.set(segment, { children: /* @__PURE__ */ new Map() });
}
if (i === segments.length - 1) {
const child = current.children.get(segment);
child.isProtected = isProtected;
}
current = current.children.get(segment);
}
}
if (segments.length === 0) {
root.isProtected = isProtected;
}
}
function matchPath(path, routeTrie, defaultProtected) {
var _a;
let cleanPath = (path.split("?")[0] || "").split("#")[0] || "";
if (cleanPath.endsWith("/") && cleanPath.length > 1) {
cleanPath = cleanPath.slice(0, -1);
}
if (cleanPath === "/") {
return (_a = routeTrie.isProtected) != null ? _a : defaultProtected;
}
const segments = cleanPath.split("/").filter(Boolean);
return findMatch(routeTrie, segments, 0, defaultProtected);
}
function findMatch(node, segments, index, defaultProtected) {
var _a, _b;
if (index >= segments.length) {
if (node.isProtected !== void 0) {
return node.isProtected;
} else if (node.catchAllChild && node.catchAllChild.isOptional) {
return (_a = node.catchAllChild.node.isProtected) != null ? _a : defaultProtected;
} else {
return defaultProtected;
}
}
const segment = segments[index];
if (node.children.has(segment)) {
const childNode = node.children.get(segment);
return findMatch(childNode, segments, index + 1, defaultProtected);
} else if (node.dynamicChild) {
return findMatch(node.dynamicChild, segments, index + 1, defaultProtected);
} else if (node.catchAllChild) {
if (node.catchAllChild.node && node.catchAllChild.node.children.size > 0) {
const tryRestSegmentMatch = (startIndex) => {
var _a2, _b2;
if (startIndex >= segments.length) {
return null;
}
const remainingSegment = segments[startIndex];
if ((_b2 = (_a2 = node.catchAllChild) == null ? void 0 : _a2.node.children) == null ? void 0 : _b2.has(remainingSegment)) {
const restNode = node.catchAllChild.node.children.get(remainingSegment);
return findMatch(restNode, segments, startIndex + 1, defaultProtected);
}
return null;
};
for (let i = index; i < segments.length; i++) {
const result = tryRestSegmentMatch(i);
if (result !== null) {
return result;
}
}
}
return (_b = node.catchAllChild.node.isProtected) != null ? _b : defaultProtected;
}
return defaultProtected;
}
// src/index.ts
function chain(functions, index = 0) {
const current = functions[index];
if (current) {
const next = chain(functions, index + 1);
return current(next);
}
return () => void 0;
}
function generateRouteMap(appDir, publicPatterns = ["(public)"], protectedPatterns = ["(protected)"]) {
if (typeof process === "undefined" || !process.env) {
return { error: "This function can only be used in a Node.js environment" };
}
try {
let scanDirectory2 = function(dirPath, segments = [], groups = []) {
if (!fs.existsSync(dirPath)) return;
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
if (item === "node_modules" || item.startsWith(".")) continue;
const isRouteGroup = item.startsWith("(") && item.endsWith(")");
const newGroups = [...groups];
const newSegments = [...segments];
if (isRouteGroup) {
newGroups.push(item);
} else {
newSegments.push(item);
}
scanDirectory2(itemPath, newSegments, newGroups);
} else if (stat.isFile() && (item === "page.js" || item === "page.tsx" || item === "page.jsx" || item === "page.ts")) {
const route = "/" + segments.join("/");
const routePath = route === "//" ? "/" : route;
let isProtected = true;
for (let i = groups.length - 1; i >= 0; i--) {
const group = groups[i];
if (group && publicPatterns.includes(group)) {
isProtected = false;
break;
} else if (group && protectedPatterns.includes(group)) {
isProtected = true;
break;
}
}
if (isProtected) {
routeMap.protected.push(routePath);
} else {
routeMap.public.push(routePath);
}
}
}
};
var scanDirectory = scanDirectory2;
const fs = __require("fs");
const path = __require("path");
const routeMap = {
public: [],
protected: []
};
scanDirectory2(appDir);
routeMap.protected.sort();
routeMap.public.sort();
return { routeMap };
} catch (error) {
return { error: error instanceof Error ? error.message : String(error) };
}
}
export {
chain,
createRouteGuardMiddleware,
generateRouteMap
};
//# sourceMappingURL=index.mjs.map