UNPKG

@lock-dev/next-adapter

Version:

Nextjs adapter module for lock.dev security framework

282 lines (276 loc) 8.42 kB
// src/index.ts import { registerModule } from "@lock-dev/core"; // src/utils/index.ts function createResponseProxy() { return { headersSent: false, writableEnded: false, statusCode: 200, headers: new Headers(), status(code) { this.statusCode = code; return this; }, setHeader(name, value) { this.headers.set(name, value); return this; }, json(body) { return new Response(JSON.stringify(body), { status: this.statusCode, headers: this.headers }); } }; } function extractSecurityMetadata(result) { if (result.event) { return { module: result.event.moduleName, reason: result.event.message, type: result.event.type, timestamp: new Date(result.event.timestamp).toISOString(), severity: result.event.severity, details: result.event.data || {} }; } else { return { module: "unknown", reason: "Access denied by security policy", timestamp: (/* @__PURE__ */ new Date()).toISOString(), details: {} }; } } function getErrorConfig(context, result) { let statusCode = 403; let errorMessage = "Access denied by security policy"; if (result.event && result.event.moduleName) { const configKey = `${result.event.moduleName}:config`; const config = context.data.get(configKey); if (config) { statusCode = config.blockStatusCode || config.statusCode || statusCode; errorMessage = config.blockMessage || config.message || errorMessage; } } return { statusCode, errorMessage }; } function createErrorData(context, result) { const metadata = extractSecurityMetadata(result); const { statusCode, errorMessage } = getErrorConfig(context, result); return { error: errorMessage, blocked: true, meta: metadata, statusCode }; } function createErrorResponse(context, result) { const errorData = createErrorData(context, result); const headers = new Headers({ "Content-Type": "application/json" }); if (context.response && "headers" in context.response) { const responseHeaders = context.response.headers; if (responseHeaders instanceof Headers) { responseHeaders.forEach((value, key) => { headers.set(key, value); }); } else if (typeof responseHeaders === "object") { Object.entries(responseHeaders).forEach(([key, value]) => { if (typeof value === "string") { headers.set(key, value); } }); } } return new Response(JSON.stringify(errorData), { status: errorData.statusCode, headers }); } function handleSecurityFailure(context, result, res) { if (!res.headersSent && !res.writableEnded) { const errorData = createErrorData(context, result); res.status(errorData.statusCode).json({ error: errorData.error, blocked: true, meta: errorData.meta }); } } // src/pages-router.ts import { composeModules, createContext } from "@lock-dev/core"; function securePagesApi(handler) { return (...modules) => { const composedModule = composeModules(...modules); return async (req, res) => { const context = createContext(req, res, {}); try { const result = await composedModule.check(context); if (result.passed) { return handler(req, res); } else { handleSecurityFailure(context, result, res); } } catch (error) { console.error("Security middleware error:", error); if (!res.headersSent) { return res.status(500).json({ error: "Internal security error" }); } } }; }; } // src/app-router.ts import { composeModules as composeModules2, createContext as createContext2 } from "@lock-dev/core"; function secureAppRoute(handler) { return (...modules) => { const composedModule = composeModules2(...modules); return async (req) => { const resProxy = createResponseProxy(); const context = createContext2(req, resProxy, {}); try { const result = await composedModule.check(context); if (result.passed) { const originalResponse = await handler(req); const responseClone = new Response(originalResponse.body, { status: originalResponse.status, statusText: originalResponse.statusText, headers: new Headers({ ...Object.fromEntries(originalResponse.headers.entries()), ...Object.fromEntries(resProxy.headers.entries()) }) }); return responseClone; } else { return createErrorResponse(context, result); } } catch (error) { console.error("Middleware error:", error); return new Response(JSON.stringify({ error: "Internal security error" }), { status: 500, headers: { "Content-Type": "application/json" } }); } }; }; } // src/server-actions.ts import { composeModules as composeModules3, createContext as createContext3 } from "@lock-dev/core"; function secureServerAction(action) { return (...modules) => { const composedModule = composeModules3(...modules); return async (...args) => { const req = { headers: {}, cookies: {}, method: "POST", url: "/", body: {} }; const formDataArg = args.find((arg) => arg instanceof FormData); if (formDataArg && formDataArg instanceof FormData) { const requestHeaders = formDataArg.get("$REQUEST_HEADERS"); if (requestHeaders && typeof requestHeaders === "string") { try { req.headers = JSON.parse(requestHeaders); } catch (e) { console.warn("Failed to parse request headers in server action"); } } const csrfToken = formDataArg.get("csrf-token") || formDataArg.get("csrfToken"); if (csrfToken && typeof csrfToken === "string") { req.headers["x-csrf-token"] = csrfToken; } } const res = { headersSent: false, writableEnded: false, statusCode: 200, headers: {}, status(code) { this.statusCode = code; return this; }, setHeader(name, value) { this.headers[name] = value; return this; }, json(body) { return body; } }; const context = createContext3(req, res, {}); try { const result = await composedModule.check(context); if (result.passed) { return action(...args); } else { const errorData = createErrorData(context, result); throw new Error(JSON.stringify(errorData)); } } catch (error) { if (error instanceof Error && error.message.startsWith("{")) { throw error; } console.error("Security middleware error:", error); throw new Error( JSON.stringify({ error: "Internal security error", statusCode: 500 }) ); } }; }; } // src/edge.ts import { composeModules as composeModules4, createContext as createContext4 } from "@lock-dev/core"; function createEdgeMiddleware(...modules) { const composedModule = composeModules4(...modules); return async function middleware(req) { const resProxy = createResponseProxy(); const context = createContext4(req, resProxy, {}); try { const result = await composedModule.check(context); if (result.passed) { const headers = new Headers({ "x-middleware-next": "1" }); resProxy.headers.forEach((value, key) => { headers.set(key, value); }); return new Response(null, { status: 200, headers }); } else { return createErrorResponse(context, result); } } catch (error) { console.error("Security middleware error:", error); return new Response(JSON.stringify({ error: "Internal security error" }), { status: 500, headers: { "Content-Type": "application/json" } }); } }; } export { createEdgeMiddleware, createErrorData, createErrorResponse, createResponseProxy, extractSecurityMetadata, getErrorConfig, handleSecurityFailure, registerModule, secureAppRoute, securePagesApi, secureServerAction };