@lock-dev/next-adapter
Version:
Nextjs adapter module for lock.dev security framework
282 lines (276 loc) • 8.42 kB
JavaScript
// 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
};