The endpoint plugin is already included in @kilpi/core. Apply the plugin in your Kilpi configuration as follows. This plugin is intended to be used together with @kilpi/client.
import { createKilpi, EndpointPlugin } from "@kilpi/core";
export const Kilpi = createKilpi({ getSubject, policies, plugins: [ EndpointPlugin({ // Required secret: process.env.KILPI_SECRET, }) ]})Usage
The endpoint plugin exposes the Kilpi.$createPostEndpoint() function which constructs a web-standard request-response handler function. You can integrate it with your framework of choice however you want. For example, with Next.js:
// (req: Request) => Promise<Response>export const POST = Kilpi.$createPostEndpoint();Optional configuration
You can provide additional configuration to EndpointPlugin to customize its behavior. See below examples.
EndpointPlugin({ secret: "...",
// Extract the `ctx` parameter from the request for `getSubject` // if required. getContext(req) { return req; },
// Before handling the request, run this hook. This hook may // optionally throw or return an early response to terminate // execution. Useful for e.g. rate-limiting. async onBeforeHandleRequest(req) { const allow = await rateLimit(); if (!allow) { return Response.json({ error: "Too fast" }, { status: 429 }); } },
// Before handling each item in the request, run this hook. // Simply an "event-listener" type callback. async onBeforeProcessItem(item) { if (item.type === "fetchDecision") { console.log(`Deciding ${item.action} on ${item.object}`); } },}); The API is designed for @kilpi/client and is not optimized for calling manually, however it is possible. See the short example below.
import * as SuperJSON from "superjson";import z from "zod";
// Utility method you can call to fetch decisions from Kilpi. Supports// batching by sending multiple queries at once.export function callKilpiApiEndpoint(body: Array<{ type: "fetchDecision"; // May support other types of requests in the future, e.g. fetchSubject requestId: string; // Generate a unique ID for this item in the array to find the corresponding response action: string; // The action, e.g. "posts.edit" object: unknown; // The corresponding object, e.g. the post object}>) { // Send the request with these parameters const response = await fetch(process.env.PUBLIC_KILPI_URL, { method: "POST", body: SuperJSON.stringify(body), headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.PUBLIC_KILPI_SECRET}`, }, });
// Response must have status 200 if (!response.ok) throw new Error("Failed to call Kilpi API");
// Setup schema for response shape using Zod const responseSchema = z.object({ // Used to match which request from the body array each // response item corresponds to. requestId: z.string(),
// The actual response data for that request. data: z.object({ decision: z.discriminatedUnion("granted", [ z.object({ granted: z.literal(true), subject: z.unknown(), }), z.object({ granted: z.literal(false), message: z.string().optional(), reason: z.string().optional(), metadata: z.unknown().optional(), }), ]), }) }).array();
// Get JSON and parse and validate return await response.json().then((body) => responseSchema.parse(body));}
const responses = await callKilpiApiEndpoint([ { type: "fetchDecision", requestId: "123", action: "posts.edit", object: { id: "post-123", authorId: "user-123" }, }])
const decision = responses.find(_ => _.requestId === "123")?.data?.decision;
if (decision.granted) { ... }