UNPKG

@tradecrush/next-route-guard

Version:

Convention-based route authentication middleware for Next.js applications

247 lines (245 loc) 8.54 kB
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