UNPKG

@centinel/nextjs

Version:

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

156 lines (155 loc) 5.41 kB
class CentinelApiValidator { constructor(config) { this.config = config; } async isBot(request) { try { // Extract client information const clientInfo = this.extractClientInfo(request); // Build validation payload const payload = this.buildValidationPayload(request, clientInfo); // Perform validation const validationResult = await this.validateRequest(payload); // Return true if bot/should be blocked, false if legitimate user // redirect should never be the case for an isBot check return !validationResult.success || ["block", "redirect"].includes(validationResult.decision); } catch (error) { console.error('Validation error:', error); // Fail open - assume not a bot on error return false; } } extractClientInfo(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") || "", }; } buildValidationPayload(request, clientInfo) { const headers = {}; request.headers.forEach((value, key) => { headers[key] = value; }); let realHost = request.headers.get("x-forwarded-host") || request.nextUrl.hostname; if (realHost === "localhost") { realHost = `localhost:${process.env.PORT}`; } const requestUrl = `${request.nextUrl.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: headers, }; } async validateRequest(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 || "block", redirect_url: result.redirect_url, cookies: result.cookies || [], }; } catch (error) { return { success: false, decision: "block", }; } } setCookies(response, cookies, request) { const realHost = this.getRealHost(request); cookies.forEach((cookie) => { response.cookies.set(cookie.name, cookie.value, { path: cookie.path || "/", domain: cookie.domain || realHost, }); }); } getRealHost(request) { const host = request.headers.get("host") || undefined; return host?.includes("localhost") ? undefined : host; } } /** * Creates a request validation function for manual validation in API routes. * * @param config - Centinel configuration object * @returns Object with verify function * * @example * ```typescript * // app/api/login/route.ts * import { createRequestValidator } from '@centinel/nextjs'; * import { NextRequest, NextResponse } from 'next/server'; * * const { isBot } = createRequestValidator({ * siteKey: process.env.CENTINEL_SITE_KEY!, * secretKey: process.env.CENTINEL_SECRET_KEY! * }); * * export async function POST(request: NextRequest) { * if (await isBot(request)) { * return NextResponse.json({ error: 'You have been blocked' }, { status: 403 }); * } * return handleLogin(request); * } * ``` */ export function createRequestValidator(config) { const validator = new CentinelApiValidator(config); return { isBot: (request) => validator.isBot(request), }; } /** * Creates request validation function using environment variables for configuration. * * @returns Object with verify function * * @example * ```typescript * // app/api/login/route.ts * import { createRequestValidatorFromEnv } from '@centinel/nextjs'; * import { NextRequest, NextResponse } from 'next/server'; * * const { isBot } = createRequestValidatorFromEnv(); * * export async function POST(request: NextRequest) { * if (await isBot(request)) { * return NextResponse.json({ error: 'You have been blocked' }, { status: 403 }); * } * return handleLogin(request); * } * ``` */ export function createRequestValidatorFromEnv() { const config = { siteKey: process.env.CENTINEL_SITE_KEY, secretKey: process.env.CENTINEL_SECRET_KEY, }; return createRequestValidator(config); } export { CentinelApiValidator };