UNPKG

@centinel/nextjs

Version:

Package designed to add Centinel Analytica functionality to Next.js applications

121 lines (120 loc) 4.67 kB
import { NextResponse } from "next/server"; export class CentinelMiddleware { constructor(config) { this.config = config; } async validate(event) { const request = event.request; try { const clientInfo = this.getClientInfo(request); const payload = this.createPayload(request, clientInfo); const validationResult = await this.callValidator(payload); if (validationResult.decision === "block") { return { response: NextResponse.redirect(new URL("/block", request.url)), decision: "block", }; } if (validationResult.decision === "redirect") { const redirectUrl = validationResult.redirect_url || "/interstitial"; const redirectResponse = NextResponse.redirect(new URL(redirectUrl, request.url)); if (validationResult.cookies?.length) { this.applyCookies(redirectResponse, validationResult.cookies, request); } return { response: redirectResponse, decision: "redirect", }; } return { response: NextResponse.next(), decision: validationResult.decision, }; } catch { return { response: NextResponse.next(), decision: "allow", }; } } getClientInfo(request) { const forwardedFor = request.headers.get("x-forwarded-for"); const realIp = request.headers.get("x-real-ip"); const cfConnectingIp = request.headers.get("cf-connecting-ip"); return { ip: cfConnectingIp || forwardedFor?.split(",")[0]?.trim() || realIp || "unknown", referer: request.headers.get("referer") || request.headers.get("referrer") || "", }; } createPayload(request, clientInfo) { const headers = {}; request.headers.forEach((value, key) => { headers[key] = value; }); // Try to get the real host from headers, fallback to nextUrl.hostname let realHost = request.headers.get("x-forwarded-host") || request.headers.get("host") || request.nextUrl.hostname; if (realHost === "localhost") { realHost = `localhost:${process.env.PORT || request.headers.get("x-forwarded-port") || "3000"}`; } // Try to get protocol from x-forwarded-proto, fallback to nextUrl.protocol const proto = request.headers.get("x-forwarded-proto") || request.nextUrl.protocol.replace(":", ""); const protocol = proto.endsWith(":") ? proto : proto + ":"; const requestUrl = `${protocol}//${realHost}${request.nextUrl.pathname}${request.nextUrl.search}`; return { cookie: request.cookies.get("_centinel")?.value, url: requestUrl, ip: clientInfo.ip, referrer: clientInfo.referer, method: request.method, headers, }; } async callValidator(payload) { try { const response = await fetch("https://validator.centinelanalytica.com/validate", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": this.config.secretKey, }, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`Validator service responded with ${response.status}`); } const result = await response.json(); return { success: result.success !== false, decision: result.decision || "allow", redirect_url: result.redirect_url, cookies: result.cookies || [], }; } catch (error) { return { success: false, decision: "allow", // Fail through }; } } applyCookies(response, cookies, request) { const realHost = this.getHost(request); cookies.forEach((cookie) => { response.cookies.set(cookie.name, cookie.value, { path: cookie.path || "/", domain: cookie.domain || realHost, }); }); } getHost(request) { const host = request.headers.get("host"); return host?.includes("localhost") ? undefined : host || undefined; } }